JS 結構型設計模式之語法優化 Module 與 Decorator 對於程式碼模組化之實踐

me
林彥成
2023-03-29 | 5 min.
文章目錄
  1. 1. 什麼是語法優化型的結構模式?
  2. 2. Module Pattern
  3. 3. Mixins Pattern
  4. 4. Proxy Pattern
  5. 5. Decorator Pattern
  6. 6. FAQ:JavaScript 語法結構模式常見問題
    1. 6.1. Q1:Module Pattern 與 ES6 Module 有什麼區別?
    2. 6.2. Q2:什麼時候該選用 Proxy Pattern 而不是單純的 Setter?
    3. 6.3. Q3:Decorator 與 Mixins 的選擇建議?

什麼是語法優化型的結構模式?

在 JavaScript 開發中,語法優化型的結構模式是指專注於如何更優雅地組織、封裝與擴展程式碼語法的設計方案。這類模式不直接改變系統的大型架構,而是透過如 Module(封裝私有作用域)、Mixins(跨物件功能共享)、Proxy(攔截並自定義物件行為)以及 Decorator(動態為物件添加職責)等技術,來提升程式碼的模組化程度、可讀性與可維護性。掌握這些模式能讓開發者在不破壞既有邏輯的前提下,更靈活地增強程式功能,是建構高品質前端應用的基礎語法策略。


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

在現代前端開發中,深入掌握 JavaScript 結構型設計模式對於程式語法優化與提升效率至關重要。本篇文章將帶您探索 Module Pattern、Mixins Pattern、Proxy Pattern JavaScriptDecorator Pattern TypeScript 等關鍵模式,幫助您更好地組織程式碼,有效提升專案的可讀性、可擴展性與維護性。這些結構型設計模式是打造穩健前端應用的基石,讓我們一起深入了解如何運用它們。

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

Design Patterns 依照目的分成三群:

Module Pattern

作為 JavaScript 結構型設計模式的基石之一,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 結構型設計模式,其概念類似於汽機車改裝零件,在不修改本體的情況下能夠優化或擴展功能。

所以以 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 JavaScript 是一種強大的 JavaScript 結構型設計模式,它允許你監控和控制對一個物件的訪問。

可以把它想像成一個代理人,當有人要訪問這個物件時,代理人可以介入並決定是否允許進行操作以及該如何處理,這對於實現程式語法優化和增強物件行為非常有用。

參考資料: 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 是一種極具彈性的 JavaScript 結構型設計模式,它允許你在不改變原有程式碼的情況下,動態地為物件新增額外的功能或行為。這對於實現程式語法優化和提升程式碼的擴展性非常有幫助。

這種模式通常是利用繼承來實現的,但是裝飾者模式強調的是可以在執行時期加上額外的功能,而不是在編譯時期就固定下來。尤其在 TypeScript 環境中,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.
}

FAQ:JavaScript 語法結構模式常見問題

Q1:Module Pattern 與 ES6 Module 有什麼區別?

A:Module Pattern 是利用閉包 (Closure) 手動實現的封裝方式,適用於任何 JS 環境;而 ES6 Module 是原生語法支援,具備更強的靜態分析能力與效能優化空間。

Q2:什麼時候該選用 Proxy Pattern 而不是單純的 Setter?

A:當您需要監控「整個物件」的所有屬性訪問,或者需要動態處理不存在的屬性(如:日誌記錄、權限驗證)時,Proxy 會比手寫每個屬性的 Getter/Setter 更加高效且具擴展性。

Q3:Decorator 與 Mixins 的選擇建議?

A:如果您想要「動態地」在執行時期替換或疊加單一物件的功能,建議使用 Decorator;如果您是想在「定義階段」就為類別增加一組通用的屬性與方法,則 Mixins 較為合適。


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