<style> .present img { margin: 5px 0 !important; } .present p { font-size: 25px; } .present code{ font-size: 19px; line-height:23px; } /* .present ul { width:100%; } */ .present li { font-size: 25px; } .present h1 { font-size: 65px; } .present h2 { font-size: 45px; /* color: #ADC7EA; */ } .present h3 { font-size: 35px; } .present h4 { font-size: 30px; } </style> # Design Pattern <!-- Put the link to this slide here so people can follow --> slide: https://hackmd.io/@WangHur/Sy2unMdQr#/ --- ## What is Design Pattern ? - 來自建築學🏠(70年代哈佛建築學博士) - 聖經: Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four,GoF) (Oct,21,1994) - 大神都在用? - 可以把code寫得好看? ---- ## What Can Design Pattern Do ? - 寫出可復用且容易維護的程式碼 - 找出程式碼中變化的部分,並且將變化封裝起來 - 幫助溝通 - 可能增加程式碼量和複雜性 ---- ![](https://i.imgur.com/ltyqcVL.png =450x) [[PDF](https://github.com/mynane/PDF/blob/master/JavaScript设计模式与开发实践.pdf)] [[重點整理](https://juejin.im/post/5c2e10a76fb9a049c0432697)] ---- ![](https://i.imgur.com/ITycc88.png) [[E-Book](https://juejin.im/post/5c2e10a76fb9a049c0432697)] --- # 3 Categories * 建立型模式(Creational patterns) * 結構型模式(Structural patterns) * 行為型模式(Behavioral patterns) --- ## 設計原則和編程技巧 ![](https://miro.medium.com/max/400/1*Ys9-TPbr_G0mFhNTthciWg.gif =300x) ![](https://i.imgur.com/b5vpGQA.png =400x) ---- ### 1.單一職責原則,Single Resposibility Priciple ![](https://i.imgur.com/7HSayG7.png =400x) ---- ### 2.最少知識原則,Least Knowledge Principle ![](https://i.imgur.com/fF62fIK.png =340x) ```javascript gerneral.getColonel(c).getMajor(m).getCaptain(c) .getSergeant(s).getPrivate(p).digFoxhole(); // 將軍,上校,少校,上尉,軍士,士兵 ``` ---- ### 3.開放-封閉原則,Open-Closed Principle ![](https://i.imgur.com/coDPckQ.png =400x) > Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification. > -Bertrand Meyer ---- ### 4. 里氏替換原則,liskov substitution principle 子類別必須能夠替換父類別,並且行為正常 ![](https://i.imgur.com/gKCnTMT.png =400x) 在鳥類中實做飛行的方法,身為繼承鳥類的企鵝非常尷尬... ---- ### 5. 依賴倒轉原則,dependency inversion 高层模块不应该[依赖](https://notfalse.net/1/dip)低层模块。两个都应该依赖抽象 People 類別內的 eat() 不應該直接依賴 burger, 而是依賴 food。 --- ## 建立型模式(Creational patterns) ### 1. 原型模式,Prototype Pattern ![](https://i.imgur.com/yHqBEzl.png =300x) - 不一定有類的存在 - 可以藉由複製創建物件 - 原型鏈 ---- ### 2. 惰性模式,Lazy Initialization Pattern <!-- Creational pattern --> 應用:登入視窗 ``` javascript= <html> <body> <button id="loginBtn">登录</button> </body> <script> var createLoginLayer = function() { var div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); return div; }; document.getElementById('loginBtn').onclick = function() { var loginLayer = createLoginLayer(); loginLayer.style.display = 'block'; }; </script> </html> ``` ---- ### 3. 單例模式,Singleton Pattern <!-- Creational pattern --> ``` javascript= var createLoginLayer = (function() { var div; return function() { if (!div) { div = document.createElement('div'); div.innerHTML = '我是登录浮窗'; div.style.display = 'none'; document.body.appendChild(div); } return div; } })(); document.getElementById('loginBtn').onclick = function() { var loginLayer = createLoginLayer(); loginLayer.style.display = 'block'; }; ``` --- ## 結構型模式,Structural Patterns ### 1. 代理模式,Proxy Pattern <!-- Structural pattern --> ![](https://i.imgur.com/8Iu4V70.png =x200)![](https://i.imgur.com/XJq5uFq.png =x200) * 保護代理 vs 虛擬代理 <!-- 过滤掉一些请求 vs 延遲開銷大的創建對象 --> * 合併HTTP請求 * 圖片預加載 ---- ``` javascript= var Flower = function() {}; var xiaoming = { sendFlower: function(target) { var flower = new Flower(); target.receiveFlower(flower); } }; var B = { receiveFlower: function(flower) { A.listenGoodMood(function() { // 监听 A 的好心情 A.receiveFlower(flower); }); } }; var A = { receiveFlower: function(flower) { console.log('收到花 ' + flower); }, listenGoodMood: function(fn) { setTimeout(function() { // 假设 10 秒之后 A 的心情变好 fn(); }, 10000); } }; xiaoming.sendFlower(B); ``` ---- ### 2. 組合模式,Composite Pattern <!-- Structural pattern 子對象,孫子對象--> <!-- ![](https://i.imgur.com/flDrrF8.png =400x) --> ![](https://i.imgur.com/dyFbfHe.png =350x) 將對象組合成樹狀結構,使得用戶對單個對象和組合對象的使用有一致性。 - 掃描文件夾 - JavaScript view frameworks ---- <!-- ![](https://i.imgur.com/SuBTGF6.png =450x) --> ![](https://i.imgur.com/nEBNCrY.png =350x) same interface between leaf and composite ``` javascript= var closeDoorCommand = { execute: function() { console.log('关门'); } }; var openPcCommand = { execute: function() { console.log('开电脑'); } }; var openQQCommand = { execute: function() { console.log('登录 QQ'); } }; var MacroCommand = function() { return { commandsList: [], add: function(command) { this.commandsList.push(command); }, execute: function() { for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); } } } }; var macroCommand = MacroCommand(); macroCommand.add(closeDoorCommand); macroCommand.add(openPcCommand); macroCommand.add(openQQCommand); macroCommand.execute(); ``` ---- ### 3. 享元模式,Flyweight Pattern <!-- Structural pattern --> 區分內部狀態 & 外部狀態 使用對象工廠:輸入外部狀態產生物件,檢查重複 ```javascript= var Model = function(sex, underwear) { this.sex = sex; this.underwear = underwear; }; Model.prototype.takePhoto = function() { console.log('sex= ' + this.sex + ' underwear=' + this.underwear); }; for (var i = 1; i <= 50; i++) { var maleModel = new Model('male', 'underwear' + i); maleModel.takePhoto(); }; for (var j = 1; j <= 50; j++) { var femaleModel = new Model('female', 'underwear' + j); femaleModel.takePhoto(); }; ``` ---- ```javascript= var Model = function(sex) { this.sex = sex; }; Model.prototype.takePhoto = function() { console.log('sex= ' + this.sex + ' underwear=' + this.underwear); }; // 分别创建一个男模特对象和一个女模特对象: var maleModel = new Model('male'), femaleModel = new Model('female'); // 给男模特依次穿上所有的男装, 并进行拍照: for (var i = 1; i <= 50; i++) { maleModel.underwear = 'underwear' + i; maleModel.takePhoto(); }; // 同样, 给女模特依次穿上所有的女装, 并进行拍照: for (var j = 1; j <= 50; j++) { femaleModel.underwear = 'underwear' + j; femaleModel.takePhoto(); }; ``` ---- The Flyweight Pattern and the DOM ```javascript= <html> <body> <ul class="menu"> <li class="item">选项1</li> <li class="item">选项2</li> <li class="item">选项3</li> <li class="item">选项4</li> <li class="item">选项5</li> <li class="item">选项6</li> </ul> </body> <script> $(".item").on("click", function () { console.log($(this).text()); }); $(".menu").on("click", ".item", function () { console.log($(this).text()); }); </script> </html> ``` ---- ### 4. 裝飾者模式,Decorator (Wrapper) Pattern <!-- Structural pattern --> 動態地給某個對象添加額外職責,而不影響到這個類的其他對象。 ```javascript= var Plane = function() {} Plane.prototype.fire = function() { console.log('发射普通子弹'); } var MissileDecorator = function(plane) { this.plane = plane; } MissileDecorator.prototype.fire = function() { this.plane.fire(); console.log('发射导弹'); } var AtomDecorator = function(plane) { this.plane = plane; } AtomDecorator.prototype.fire = function() { this.plane.fire(); console.log('发射原子弹'); } var plane = new Plane(); plane = new MissileDecorator(plane); plane = new AtomDecorator(plane); plane.fire(); // 分别输出: 发射普通子弹、发射导弹、发射原子弹 ``` ---- ![](https://i.imgur.com/ODKghWH.png =350x) * 數據統計上報 * 動態改變函數參數 * 插件式的表單驗證 遵守開放-封閉原則、避免覆蓋 ```javascript= window.onload = function() { alert(1); } var _onload = window.onload || function() {}; window.onload = function() { _onload(); alert(2); } ``` ---- ### 5. 轉接器模式,Adapter Pattern <!-- Structural pattern --> ![](https://i.imgur.com/8TRYwCn.png =200x) ```javascript= var googleMap = { show: function() { console.log('开始渲染谷歌地图'); } }; var baiduMap = { display: function() { console.log('开始渲染百度地图'); } }; var baiduMapAdapter = { show: function() { return baiduMap.display(); } }; renderMap(googleMap); // 输出:开始渲染谷歌地图 renderMap(baiduMapAdapter); // 输出:开始渲染百度地图 ``` --- ## 行為型模式,Behavioral Patterns ### 1. 策略模式,Strategy Pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/28PonaW.png =300x) ```javascript= var calculateBonus = function(performanceLevel, salary) { if (performanceLevel === 'S') { return salary * 4; } if (performanceLevel === 'A') { return salary * 3; } if (performanceLevel === 'B') { return salary * 2; } }; calculateBonus('B', 20000); // 输出:40000 calculateBonus('S', 6000); // 输出:24000 ``` ---- Strategy策略類,Context環境類 ```javascript= var strategies = { "S": function(salary) { return salary * 4; }, "A": function(salary) { return salary * 3; }, "B": function(salary) { return salary * 2; } }; var calculateBonus = function(level, salary) { return strategies[level](salary); }; console.log(calculateBonus('S', 20000)); // 输出:80000 console.log(calculateBonus('A', 10000)); // 输出:30000 ``` ---- ### 2. 迭代器模式,Iterator pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/N9swyHN.png =350x) 內部迭代器 ```javascript= var each = function(ary, callback) { for (var i = 0, l = ary.length; i < l; i++) { callback.call(ary[i], i, ary[i]); // 把下标和元素当作参数传给 callback 函数 } }; ``` ---- 外部迭代器 ```javascript= var Iterator = function(obj) { var current = 0; var next = function() { current += 1; }; var isDone = function() { return current >= obj.length; }; var getCurrItem = function() { return obj[current]; }; return { next: next, isDone: isDone, getCurrItem: getCurrItem }; } ``` ---- ### 3. 發布/訂閱模式,Publish/Subscribe Pattern <!-- Behavioral pattern --> #### Dom 事件 ```javascript= document.body.addEventListener('click', function() { alert(2); }, false); document.body.click(); // 模拟用户点击 ``` ---- #### 小明和售樓處的故事🏡 ```javascript= var salesOffices = {}; // 定义售楼处 salesOffices.clientList = []; // 缓存列表,存放订阅者的回调函数 salesOffices.listen = function(fn) { // 增加订阅者 this.clientList.push(fn); // 订阅的消息添加进缓存列表 }; salesOffices.trigger = function() { // 发布消息 for (var i = 0, fn; fn = this.clientList[i++];) { fn.apply(this, arguments); // arguments 是发布消息时带上的参数 } }; // 下面我们来进行一些简单的测试: salesOffices.listen(function(price, squareMeter) { // 小明订阅消息 console.log('价格= ' + price); console.log('squareMeter= ' + squareMeter); }); salesOffices.listen(function(price, squareMeter) { // 小红订阅消息 console.log('价格= ' + price); }); salesOffices.trigger(2000000, 88); salesOffices.trigger(3000000, 110); ``` 應用:網頁登錄 ---- ### 4.命令模式,Command Pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/VpMjWh5.png =300x) - 解開命令調用者和接收者之間的耦合關係 - 命令對象有較長的生命週期 - 撤銷、排隊 ---- 菜單刷新程序 ```javascript= var MenuBar = { refresh: function() { console.log('刷新菜单目录'); } }; var RefreshMenuBarCommand = function(receiver) { return { execute: function() { receiver.refresh(); } } }; var setCommand = function(button, command) { button.onclick = function() { command.execute(); } }; var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar); setCommand(button1, refreshMenuBarCommand); ``` ---- ### 5.模板方法模式,Template Method Pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/7Jj7zkP.png =750x) Coffee or Tea ☕️ - 依賴於繼承和抽象類(JavaScript 不提供) - 抽象方法檢查: - 鴨子模型🐥 - 拋出異常 - 鉤子方法(hook) ---- ```javascript= var Beverage = function() {}; Beverage.prototype.boilWater = function() { console.log('把水煮沸'); }; Beverage.prototype.brew = function() { throw new Error('子类必须重写 brew 方法'); }; Beverage.prototype.pourInCup = function() { throw new Error('子类必须重写 pourInCup 方法'); }; Beverage.prototype.addCondiments = function() { throw new Error('子类必须重写 addCondiments 方法'); }; Beverage.prototype.init = function() { this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); }; var Coffee = function() {}; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function() { console.log('用沸水冲泡咖啡'); }; Coffee.prototype.pourInCup = function() { console.log('把咖啡倒进杯子'); }; Coffee.prototype.addCondiments = function() { console.log('加糖和牛奶'); }; var Coffee = new Coffee(); Coffee.init(); ``` ---- ### 6.職責鏈模式,Chain of Responsibility Pattern <!-- Behavioral pattern --> 請求發送者只需要知道第一個節點,解開發送者和**一組**接收者的耦合。 ![](https://i.imgur.com/ygSg28c.png =300x) ```javascript= var Request = function(amount) { this.amount = amount; log.add("Requested: $" + amount + "\n"); } Request.prototype = { get: function(bill) { var count = Math.floor(this.amount / bill); this.amount -= count * bill; log.add("Dispense " + count + " $" + bill + " bills"); return this; } } function run() { var request = new Request(378); request.get(100).get(50).get(20).get(10).get(5).get(1); log.show(); } // log helper var log = (function() { var log = ""; return { add: function(msg) { log += msg + "\n"; }, show: function() { alert(log); log = ""; } } })(); ``` ---- ### 7.中介者模式,Mediator Pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/GMv7ZAt.png =x200)![](https://i.imgur.com/iWR8hT7.png =x200) 符合最少知識原則 中介者對象常常變得龐大且複雜 - 炸彈超人、爆爆王(泡泡堂) - 聊天室 ---- ### 8.State Pattern <!-- Behavioral pattern --> ![](https://i.imgur.com/VYVuhQK.png =300x) 違反開放-封閉原則 ```javascript= var Light = function() { this.state = 'off'; this.button = null; }; Light.prototype.init = function() { var self = this; $('<button>').text('開關').appendTo('body').click(function() { self.buttonWasPressed(); }); }; Light.prototype.buttonWasPressed = function() { if (this.state === 'off') { console.log('開'); this.state = 'on'; } else { console.log('關'); this.state = 'off'; } }; var light = new Light(); light.init(); ``` ---- ```javascript= var OffLightState = function(light) { this.light = light; }; //按鈕被按之後要做的事 OffLightState.prototype.buttonWasPressed = function() { console.log('弱光'); this.light.setState(this.light.weakLightState); //切換到弱光的狀態 }; ``` ```javascript= var Light = function() { this.offLightState = new OffLightState(this); this.weakLightState = new WeakLightState(this); this.strongLightState = new StrongLightState(this); this.button = null; }; Light.prototype.init = function() { var self = this; this.current = this.offLightState; //設置初始狀態 $('<button>').text('開關').appendTo('body').click(function() { self.current.buttonWasPressed(); }); }; Light.prototype.setState = function(newState) { this.current = newState; }; var light = new Light(); light.init(); ``` --- ## Some Observations 👀 - 有些設計模式很類似,差在關注對象或應用場景不同 - 遵守一樣的原則和概念 - 有時候只看最簡單的範例不會有感覺,看後面的複雜範例才會理解容易復用的優點。 - 可以再讀一本用Java介紹的書 --- ## Thanks for listening 🙇‍♂️
{"metaMigratedAt":"2023-06-14T23:17:24.773Z","metaMigratedFrom":"YAML","title":"Design Pattern","breaks":true,"description":"Design Pattern","contributors":"[{\"id\":\"5a38a7fc-4063-4caa-9a7c-f1b8473b7cb0\",\"add\":21328,\"del\":6258}]"}
    495 views