<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 ?
- 寫出可復用且容易維護的程式碼
- 找出程式碼中變化的部分,並且將變化封裝起來
- 幫助溝通
- 可能增加程式碼量和複雜性
----

[[PDF](https://github.com/mynane/PDF/blob/master/JavaScript设计模式与开发实践.pdf)]
[[重點整理](https://juejin.im/post/5c2e10a76fb9a049c0432697)]
----

[[E-Book](https://juejin.im/post/5c2e10a76fb9a049c0432697)]
---
# 3 Categories
* 建立型模式(Creational patterns)
* 結構型模式(Structural patterns)
* 行為型模式(Behavioral patterns)
---
## 設計原則和編程技巧


----
### 1.單一職責原則,Single Resposibility Priciple

----
### 2.最少知識原則,Least Knowledge Principle

```javascript
gerneral.getColonel(c).getMajor(m).getCaptain(c) .getSergeant(s).getPrivate(p).digFoxhole();
// 將軍,上校,少校,上尉,軍士,士兵
```
----
### 3.開放-封閉原則,Open-Closed Principle

> Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification.
> -Bertrand Meyer
----
### 4. 里氏替換原則,liskov substitution principle
子類別必須能夠替換父類別,並且行為正常

在鳥類中實做飛行的方法,身為繼承鳥類的企鵝非常尷尬...
----
### 5. 依賴倒轉原則,dependency inversion
高层模块不应该[依赖](https://notfalse.net/1/dip)低层模块。两个都应该依赖抽象
People 類別內的 eat() 不應該直接依賴 burger,
而是依賴 food。
---
## 建立型模式(Creational patterns)
### 1. 原型模式,Prototype Pattern

- 不一定有類的存在
- 可以藉由複製創建物件
- 原型鏈
----
### 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 -->

* 保護代理 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 子對象,孫子對象-->
<!--  -->

將對象組合成樹狀結構,使得用戶對單個對象和組合對象的使用有一致性。
- 掃描文件夾
- JavaScript view frameworks
----
<!--  -->

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(); // 分别输出: 发射普通子弹、发射导弹、发射原子弹
```
----

* 數據統計上報
* 動態改變函數參數
* 插件式的表單驗證
遵守開放-封閉原則、避免覆蓋
```javascript=
window.onload = function() {
alert(1);
}
var _onload = window.onload || function() {};
window.onload = function() {
_onload();
alert(2);
}
```
----
### 5. 轉接器模式,Adapter Pattern
<!-- Structural pattern -->

```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 -->

```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 -->

內部迭代器
```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 -->

- 解開命令調用者和接收者之間的耦合關係
- 命令對象有較長的生命週期
- 撤銷、排隊
----
菜單刷新程序
```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 -->

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 -->
請求發送者只需要知道第一個節點,解開發送者和**一組**接收者的耦合。

```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 -->

符合最少知識原則
中介者對象常常變得龐大且複雜
- 炸彈超人、爆爆王(泡泡堂)
- 聊天室
----
### 8.State Pattern
<!-- Behavioral pattern -->

違反開放-封閉原則
```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}]"}