JavaScript Structural Pattern For Program Syntax 開箱 JavaScript 設計模式 (2-1)

me
林彥成
2023-03-29 | 3 min.
文章目錄
  1. 1. Module Pattern
  2. 2. Mixins Pattern
  3. 3. Proxy Pattern
  4. 4. Decorator Pattern

你知道 React 本身也有使用設計模式嗎? 其中用到最多的又會是哪一種?

以 React 架構上來說,因為是 Component Based 的函式庫,最常用到的會是 Module Pattern,而實現 Component 的實作則是 Facade Pattern。

上一篇文章介紹過了創建型的 Pattern,這篇文章會繼續開箱結構型,結構型可以幫助我們更好的去組織程式碼,避免隨著專案規模增長找到程式碼的難度也越來越高的現象,接下來會先從優化語法的四種結構型設計模式開始介紹

  • Module Pattern
  • Mixins Pattern
  • Proxy Pattern
  • Decorator Pattern

Design Patterns 依照目的分成三群:

Module Pattern

Module pattern 是一種設計模式,主要用於封裝功能,可以將變數和函數進行分組並且限制在一個私有作用域,只將公共介面揭露出來,減少和全域變數、函式發生衝突。

以下是一個簡單的 Module Pattern 範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const moduleCounter = (function () {
let count = 0;
return {
getValue: function () {
return count;
},
increment: function () {
return ++count;
},
reset: function () {
console.log("reset:" + count);
count = 0;
},
};
})();

moduleCounter.getValue();
moduleCounter.increment();
moduleCounter.reset();

在這個範例中,moduleCounter 是一個封裝私有變數和函數的模組。

  • count 是私有變數,無法從模組外部存取
  • getValue 是一個公開的函式用來存取私有的變數

在 ES6 中,JavaScript 提供了一種內建的模組系統,可以通過 export 和 import 關鍵字來處理。

這種模組系統使得 JavaScript 模組化開發更加容易,提高了可讀性和可維護性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// counter.js
let count = 0;

export function getValue() {
return count;
}

export function increment() {
return ++count;
}

export function reset() {
console.log("reset:" + count);
count = 0;
}
1
2
3
import { getValue, increment, reset } from "./counter.js";

console.log(getValue());

Mixins Pattern

Mixins Pattern 像是汽機車改裝零件,在本體不被修改的情況下能夠優化一些功能。

所以以 JavaScript 來說,透過 prototype 的特性可以在不修改現有程式碼的情況下,將物件添加新的功能或屬性。

Mixins Pattern 將多個物件的功能和屬性混合在一起,建立一個新的物件,這個新的物件包含了所有混合在一起的功能和屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 1. 定義基礎 Class 一台機車包含牌照及 CC 數
class Motorcycle {
constructor(license, volume) {
this.license = license;
this.volume = volume;
}
}

// 2. 定義 Sym 工廠並且定義 createMotor function
class MotorFactory {
createMotor({ volume }) {
if (volume <= 50) {
return new Motorcycle("green", volume);
}

if (volume < 150) {
return new Motorcycle("white", volume);
}
}
}

// 3. 定義 mixins
const motorComponentMixins = {
makeHornSound() {
console.log(`${this.license} ba ba`);
},
};

Object.assign(Motorcycle.prototype, motorComponentMixins);

// 建立工廠物件
const motorFactory = new MotorFactory();

// 透過工廠物件生產機車
const motorGreenLicense = motorFactory.createMotor({ volume: 50 });
const motorWhiteLicense = motorFactory.createMotor({ volume: 125 });

console.log(motorGreenLicense);
console.log(motorWhiteLicense);

motorGreenLicense.makeHornSound();

Proxy Pattern

Proxy Pattern 可以監控和控制對一個物件的訪問。

可以把它想像成一個代理人,當有人要訪問這個物件時,代理人可以介入並決定是否允許進行操作以及該如何處理。

參考資料: https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Proxy

舉個例子,假設我們有一個物件 person,裡面有一個屬性 age,並且在 age 屬性加上一些控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let validator = {
set: function (obj, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("The age is not an integer");
}
if (value > 200) {
throw new RangeError("The age seems invalid");
}
if (value > 18) {
console.log("Can ride motorcycle with license");
}
}

// The default behavior to store the value
obj[prop] = value;

// Indicate success
return true;
},
};

let person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = "young"; // Throws an TypeError exception
person.age = 300; // Throws an RangeError exception

Decorator Pattern

Decorator Pattern 可以在不改變原有程式碼的情況下,動態幫物件新增額外的功能。

這種模式通常是利用繼承來實現的,但是裝飾者模式強調的是可以在執行時期加上額外的功能,而不是在編譯時期就固定下來。

可以想像成一杯咖啡(被裝飾者)可以通過添加不同的裝飾物(裝飾者)來增加不同的風味。

例如,如果想要加牛奶、加糖,這樣咖啡就同時擁有了糖和牛奶。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 原有的機車
class Motorcycle {
constructor() {
this.price = 0;
this.description = "Basic Motorcycle";
}
getPrice() {
return this.price;
}
getDescription() {
return this.description;
}
}

// 改裝後的機車 (裝飾者)
class CustomizedMotorcycle {
constructor(motorcycle) {
this.motorcycle = motorcycle;
}
getPrice() {
return this.motorcycle.getPrice() + 1000;
}
getDescription() {
return this.motorcycle.getDescription() + " with Customized Parts";
}
}

// 性能優化的機車 (裝飾者)
class HighPerformanceMotorcycle {
constructor(motorcycle) {
this.motorcycle = motorcycle;
}
getPrice() {
return this.motorcycle.getPrice() + 2000;
}
getDescription() {
return this.motorcycle.getDescription() + " with High Performance Parts";
}
}

// 使用
let basicMotorcycle = new Motorcycle();
console.log(
basicMotorcycle.getDescription() + " costs $" + basicMotorcycle.getPrice()
);

let customizedMotorcycle = new CustomizedMotorcycle(basicMotorcycle);
console.log(
customizedMotorcycle.getDescription() +
" costs $" +
customizedMotorcycle.getPrice()
);

let highPerformanceMotorcycle = new HighPerformanceMotorcycle(
customizedMotorcycle
);
console.log(
highPerformanceMotorcycle.getDescription() +
" costs $" +
highPerformanceMotorcycle.getPrice()
);

在 TypeScript 中,Decorator 是一個 function, 透過在 class/parameter/method/property 前面加上 @ 來使用,這種寫法在 Angular 中被大量使用。

參考資料: https://www.typescriptlang.org/docs/handbook/decorators.html

1
2
3
4
5
6
7
8
9
10
11
12
import { Component } from "@angular/core";

@Component({
selector: "hello-world",
template: `
<h2>Hello World</h2>
<p>This is my first component!</p>
`,
})
export class HelloWorldComponent {
// The code in this class drives the component's behavior.
}

喜歡這篇文章,請幫忙拍拍手喔 🤣


share