/* S.O.L.I.D五大原則 1. 單一原則 2. 開閉原則 3. 里氏替換原則 4. 接口分離原則 5. 依賴反轉原則 */ /*單一原則:類發生更改的緣由只有一個 如何知道一個對象的多個行爲構造多個職責或單個職責? 判斷標準: 1. 存儲對象:提供對象信息給其餘對象 2. 維護對象:維護對象和信息之間的關係 3. 服務對象:處理工做並提供服務給其餘對象 4. 控制對象:控制決策一系列負責的任務處理 5. 協調對象:不作處理工做,只是delegate工做到其餘對象 5. 接口對象:在系統各個部分轉化信息(或請求) */ //商品實體,有ID和描述 function Product(id, desc) { this.getId = function () { return id; } this.getDesc = function () { return desc; } } //購物車,添加清單 function Cart(evetAggregator) { var items = []; this.addItem = function (item) { items.push(item); } } (function () { //初始化一系列商品 var products = [new Product(1, "a"), new Product(2, "b"), new Product(3, "c")], cart = new Cart(); //商品雙擊後獲取ID,將商品添加到CART中 function addToCart() { var productId = $(this).attr('id'); var product = $.grep(products, function (x) { return x.getId() == productId; })[0]; cart.addItem(product); var newItem = $('<li></li>').html(product.getDesc()).attr('id-cart', product.getId()).apendTo('#cart'); } //將商品遍歷,初始化事件,添加到商品集合中 products.forEach(function (prodcuct) { var newItem = $('<li></li>').html(product.getDesc()).attr('id', product.getId()).dbclick(addToCart).apendTo("#products"); }); }()); /* 如上,職責分析 1. 有products的聲明 2. 將product集合綁定到#products中,同時添加購物車事件處理 3. 有購物車展現功能 4. 添加產品到購物車並顯示功能 */ /* 採用事件聚合理論(event aggreagator)優化,分爲2部分 1. Event,用於Handler回調代碼 2. EventAggregator,訂閱和發佈事件 */ function Event(name) { var handlers = []; this.getName = function () { return name; }; this.addHandler = function (handler) { handlers.push(handler); }; this.removeHandler = function (handler) { for (var i = 0; i < handlers.length; i++) { if (handlers[i]==handler) { handlers.slice(i, 1); break; } } }; this.fire = function (eventArgs) { handlers.forEach(function (h) { h(eventArgs); }); } } function EventAggregator() { var events = []; function getEvent(eventName) { return $.grep(events, function (x) { return event.getName() == eventName; })[0]; } this.publish = function (eventName, eventArgs) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.fire(eventArgs); } this.subscribe = function (eventName,handler) { var event = getEvent(eventName); if (!event) { event = new Event(eventName); events.push(event); } event.addHandler(handler); } } //修改Cart function Cart(eventAggregator) { var items = []; this.addItem = function (item) { items.push(item); eventAggregator.publish('itemAdd', item); } } //新增CartController,接收cart對象的事件聚合器,經過訂閱itemAdd來增長一個li節點,經過訂閱productSelected事件來添加product function CartController(cart, eventAggregator) { eventAggregator.subscribe('itemAdd', function(eventArgs) { var newItem = $('<li></li>').html(eventArgs.getDesc()).attr('id-cart', eventArgs.getId()).appendTo("#cart"); }); eventAggregator.subscribe("productSelected", function (eventArgs) { cart.addItem(eventArgs.product); }); } //新增repository:ajax獲取數據,暴露get方法 function ProductRepository() { var products = [new Product(1, "a"), new Product(2, "b"), new Product(3, "c")]; this.getProducts = function () { return products; } } //新增ProductController: 定義OnProductSelect方法,主要發佈觸發ProductSelected事件,forEach主要用於綁定數據到產品列表上 function ProductController(eventAggregator,productRepository) { var products = productRepository.getProducts(); function onProductSelected() { var productId = $(this).attr('id'); var product = $.grep(products, function (x) { return x.getId() == productId; })[0]; eventAggregator.publish('productSelected', { product: product }); } products.forEach(function (product) { var newItem = $('<li></li>').html(product.getDesc()).attr('id', product.getId()).dblclick(onProductSelected).appendTo("#products"); }); } //最後聲明匿名函數 (function () { var eventAggregator = new EventAggregator(), cart = new Cart(eventAggregator), cartController = new CartController(cart, eventAggregator), productRepository = new ProductRepository(), productController = new ProductController(eventAggregator, productRepository); }()); /* 開閉原則:軟件開發對外擴展開發,對內修改關閉,即在不修改的前提下擴展 */ //1、問題代碼 //問題類型 var AnswerType = { Choice: 0, Inut: 1 } //定義問題實體 function question(label, answerType, choices) { return { label: label, answerType: answerType, choices: choices }; } var view = (function () { function renderQuestion(target, question) { //建立最外層DIV var questionWrapper = document.createElement("div"); var answer = document.createElement('div'); //當問題爲下拉選擇模式 if (question.answerType == AnswerType.Choice) { var input = document.createElement('select'); var len = question.choices.length; for (var i = 0; i < len; i++) { var option = document.createElement('option'); option.text = question.choices[i]; option.value = question.choices[i]; input.appendChild(option); } } else if (question.answerType == AnswerType.Inut) { var input = document.createElement('input'); input.type = 'text'; } answer.appendChild(input); questionWrapper.appendChild(questionLabel); questionWrapper.appendChild(answer); target.appendChild(questionWrapper); } // 遍歷全部的問題列表進行展現 return { rendeer: function (target, questions) { for (var i = 0; i < questions.length; i++) { renderQuestion(target, questions[i]); } } } }()); /* 問題代碼分析: 違背開閉原則,若是增長一個question類型,則須要再次修改renderQuestion函數 */ //choiceQuestionCreator函數和inputQuestionCreator函數分別對應下拉菜單和input輸入框的renderInput實現,經過內部調用統一的questionCreator(spec, my)而後返回that對象(同一類型哦)。 function questionCreator(spec, my) { var that = {}; my = my || {}; my.label = spec.label; my.renderInput = function () { throw "not implemented"; } that.render = function (target) { var questionWrapper = document.createElement("div"); var answer = my.renderInput(); questionWrapper.appendChild(answer); return questionWrapper; } return that; } function choiceQuestionCreator(spec, my) { var my = {}, that = questionCreator(spec, my); //實現renderInput my.renderInput = function () { var input = document.createElement('select'); var len = spec.choices.length; for (var i = 0; i < len; i++) { var option = document.createElement('option'); option.text = spec.choices[i]; option.value = spec.choices[i]; input.appendChild(option); } return input; } return that; } function inputQuestionCreator(spec, my) { var my = {}, that = questionCreator(spec, my); // input類型的renderInput實現 my.renderInput = function () { var input = document.createElement('input'); input.type = 'text'; return input; }; return that; } var view = { render: function (target, questions) { for (var i = 0; i < questions.length; i++) { target.appendChild(question[i].render()); } } } var question = [ choiceQuestionCreator({ label: '', choices: ['yes', 'no'] }), inputQuestionCreator({ label: '' }) ]; var questionRegion = document.getElementById('questions'); view.render(questionRegion, questions); /* 1. 首先,questionCreator採用模板方法模式將處理問題的功能delegate給每一個問題類型擴展代碼renderInput上 2. 其次,用spec替換替換label、choices,防止屬性暴露給外部代碼 3. 而後,每一個實現都必須含有renderInput覆蓋原有的questionCreator方法裏的renderInput代碼:策略模式 */ /* 里氏替換原則:派生類必須能夠替換他的基類型 */ //1、定義Vehicle函數,提供基本操做 function Vehicle(my) { var my = my || {}; my.speed = 0; my.running = false; this.speed = function () { return my.speed; } this.start = function () { my.running = true; } this.stop = function () { my.running = false; } this.accelerate = function() { my.speed++; }; this.decelerate = function () { my.speed--; } return my; } //FastVehicle違背里氏替換原則由於加減速的數據不同。子類已非等價於基類的指望行爲 function FastVehicle(my) { var my = my || {}; var that = new Vehicle(my); that.accelerate = function () { my.speed += 3; }; return that; } /* 2、減小LSP妨礙 1. 使用契約 a.檢查使用TDD來指導代碼設計 b.設計可重用類庫時候可隨意使用契約設計技術 2. 避免繼承,多組合 */ // 3、與行爲有關,而不是繼承 // LSP本質是:行爲兼容性,並不是繼承 /* 接口隔離原則 Interface Segregation Principle 不該該強迫用戶依賴於他們不一樣的方法 */ // 1、JS沒有接口的概念,但能夠當作一個contract提供約束 var myBindApply = {}; myBindApply.modelObserver = (function () { //私有變量 return { observe: function (model) { /*代碼*/ return newModel }, onChange: function (callback) { /*代碼*/ } }; }()); myBindApply.viewAdaptor = (function () { return { bind: function (model) { } }; }()); //公共接口是bind方法 myBindApply.bind = function (model) { /*私有變量*/ myBindApply.modelObserver.onChange(function () { }); var vm = myBindApply.modelObserver.observe(model); myBindApply.viewAdaptor.bind(vm); }; /* 依賴倒置原則 Dependency Inversion Principle 高層模塊不依賴於底層模塊,兩者應該依賴於抽象。 抽象不依賴於細節,細節依賴於抽象 */ /* 舉例:傳統架構中,高層模塊(封裝了核心業務邏輯)總依賴一些基礎點模塊 ==》 依賴倒置 ==》底層模塊依賴高層模塊定義的接口 傳統程序持久化依賴於持久化模塊API ==》 依賴倒置 ==》 核心模塊須要定義持久化API接口,而後持久化模塊須要實現這個API接口 */ //在JavaScript裏,依賴倒置原則的適用性僅僅限於高層模塊和低層模塊之間的語義耦合,好比,DIP能夠根據須要去增長接口而不是耦合低層模塊定義的隱式接口。 $.fn.traceMap = function (options) { var defaults = {}; options = $.extend({}, defaults, options); var mapOptions = { center: new google.maps.LatLng(options.latitude, options.longitude), zoom: 12 }, map = new google.maps.Map(this[0], mapOptions); options.fee.update(function () { map.setCenter(); }); return this; }; var updater = (function () { return { update: function (callback) { updateMap = callback; } }; })(); $("#map_canvas").trackMap({ feed: updater }); //trackMap函數有2個依賴:第三方的Google Maps API和Location feed //解決方案:消除MAP依賴,提供trackMap.googleMapsProvider隱式接口(抽象出地圖提供商provider接口) $.fn.trackMap = function (options) { var defaults = { /* defaults */ }; options = $.extend({}, defaults, options); options.provider.showMap(this[0], options.latitude, options.longitude, options.icon, options.title); options.feed.update( function (latitude, longitude) { options.provider.updateMap(latitude, longitude); }); return this; }; $("#map_canvas").trackMap( { latitude: 35.044640193770725, longitude: -89.98193264007568, icon: 'http://bit.ly/zjnGDe', title: 'Tracking Number: 12345', feed: updater, provider: trackMap.googleMapsProvider }); //配合MaP API的實現對象 trackMap.googleMapsProvider = (function () { var marker, map; return { showMap: function (element, latitude, longitude, icon, title) { var mapOptions = { center: new google.maps.LatLng(latitude, longitude), zoom: 12, mapTypeId: google.maps.MapTypeId.ROADMAP }, pos = new google.maps.LatLng(latitude, longitude); map = new google.maps.Map(element, mapOptions); marker = new google.maps.Marker({ position: pos, title: title, icon: icon }); marker.setMap(map); }, updateMap: function (latitude, longitude) { marker.setMap(null); var newLatLng = new google.maps.LatLng(latitude, longitude); marker.position = newLatLng; marker.setMap(map); map.setCenter(newLatLng); } }; })(); /* 什麼時候依賴注入? 依賴注入是控制反轉的一個特殊形式,反轉的意思一個組件如何獲取它的依賴。 依賴注入的意思就是:依賴提供給組件,而不是組件去獲取依賴, 意思是建立一個依賴的實例,經過工廠去請求這個依賴,經過Service Locator或組件自身的初始化去請求這個依賴。 依賴倒置原則和依賴注入都是關注依賴,而且都是用於反轉。不過,依賴倒置原則沒有關注組件如何獲取依賴,而是隻關注高層模塊如何從低層模塊裏解耦出來。 某種意義上說,依賴倒置原則是控制反轉的另一種形式,這裏反轉的是哪一個模塊定義接口(從低層裏定義,反轉到高層裏定義)。 */