你知道 React 本身也有使用設計模式嗎? 其中用到最多的又會是哪一種?
以 React 架構上來說,因為是 Component Based 的函式庫,最常用到的會是 Module Pattern,而實現 Component 的實作則是 Facade Pattern。
上一篇文章介紹過了創建型的 Pattern,這篇文章會繼續開箱結構型,結構型可以幫助我們更好的去組織程式碼,避免隨著專案規模增長找到程式碼的難度也越來越高的現象,接下來會先從優化語法的四種結構型設計模式開始介紹
- Module Pattern
- Mixins Pattern
- Proxy Pattern
- Decorator Pattern
Design Patterns 依照目的分成三群:
- Creational Patterns 創建型
- Structural Patterns 結構型,結構型設計模式根據實際情況會分成三種:
- Behavioural Patterns 行為型
Module Pattern
Module pattern 是一種設計模式,主要用於封裝功能,可以將變數和函數進行分組並且限制在一個私有作用域,只將公共介面揭露出來,減少和全域變數、函式發生衝突。
以下是一個簡單的 Module Pattern 範例:
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 模組化開發更加容易,提高了可讀性和可維護性。
// counter.js
let count = 0;
export function getValue() {
return count;
}
export function increment() {
return ++count;
}
export function reset() {
console.log("reset:" + count);
count = 0;
}
import { getValue, increment, reset } from "./counter.js";
console.log(getValue());
Mixins Pattern
Mixins Pattern 像是汽機車改裝零件,在本體不被修改的情況下能夠優化一些功能。
所以以 JavaScript 來說,透過 prototype 的特性可以在不修改現有程式碼的情況下,將物件添加新的功能或屬性。
Mixins Pattern 將多個物件的功能和屬性混合在一起,建立一個新的物件,這個新的物件包含了所有混合在一起的功能和屬性。
// 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 屬性加上一些控制
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 可以在不改變原有程式碼的情況下,動態幫物件新增額外的功能。
這種模式通常是利用繼承來實現的,但是裝飾者模式強調的是可以在執行時期加上額外的功能,而不是在編譯時期就固定下來。
可以想像成一杯咖啡(被裝飾者)可以通過添加不同的裝飾物(裝飾者)來增加不同的風味。
例如,如果想要加牛奶、加糖,這樣咖啡就同時擁有了糖和牛奶。
// 原有的機車
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
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.
}
喜歡這篇文章,請幫忙拍拍手喔 🤣