JavaScript 以其廣泛的採用和多功能性,已成為現代 Web 開發的基石。隨著您深入研究 JavaScript 開發,理解和利用模式變得至關重要。在本文中,我們將踏上揭開 JavaScript 模式神秘面紗的旅程,並探索它們如何增強您的程式設計實踐。
要理解本文中討論的概念和技術,您需要了解 JavaScript 的基礎知識。熟悉變數、函數、資料類型、物件導向程式設計等概念至關重要。
在繼續之前,讓我們花點時間了解 JavaScript 作為程式語言的重要性。
JavaScript 通常被稱為“網路語言”,是一種動態的高階程式語言。它主要用於 Web 瀏覽器中的客戶端腳本編寫,但隨著 Node.js 的出現,它也在伺服器端獲得了關注。 JavaScript 的主要功能包括操作 DOM、處理事件、為網頁提供互動性等的能力。
話雖這麼說,讓我們簡單討論一下 JavaScript 中模式的重要性和用途。
JavaScript 中的模式可以作為軟體開發過程中遇到的重複問題的經過驗證的解決方案。它們提供結構、改進程式碼組織、增強可維護性並促進可重複使用性。透過理解和應用模式,開發人員可以編寫更清晰、更有效率的程式碼並有效應對複雜的挑戰。
理解 JavaScript 模式不僅僅是記住文法或遵循最佳實踐。它使開發人員能夠批判性地思考軟體設計、選擇適當的解決方案並建立可擴展的應用程式。透過掌握 JavaScript 模式,您可以深入了解該語言及其生態系統,從而能夠編寫健全且可維護的程式碼。
現在我們知道了 JavaScript 模式的重要性和用途,讓我們深入研究 JS 設計模式的基礎知識。
在本節中,我們為理解 JavaScript 開發背景下的設計模式奠定了基礎。
設計模式是可重複使用的模板,封裝了解決重複出現的軟體設計問題的最佳實踐。它們提供了一種結構化的方法來設計軟體系統,並促進模組化、靈活和可維護的程式碼。設計模式的共同特徵包括其目的、結構、參與者和協作。
設計模式可分為三種主要類型:
創意
結構性
行為的
了解這些類別有助於確定給定問題的適當模式。
建立模式專注於物件建立機制,提供以靈活且受控的方式實例化物件的方法。 JavaScript 中一些常用的建立模式包括:
辛格頓
工廠
建構函數
原型
建造者
模組
單例模式
單例模式確保一個類別只有一個實例,並提供對其的全域存取點。當您想要限制類別的實例數量並確保在整個應用程式中可以存取單一共用實例時,此模式非常有用。
// Implementation example of the Singleton Pattern
class Singleton {
constructor() {
if (!Singleton.instance) {
// Initialize the instance
Singleton.instance = this;
}
return Singleton.instance;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: true
在此範例中,Singleton 類別有一個建構函數,用於檢查該類別的實例是否已存在。如果實例不存在(“!Singleton.instance”條件),它將透過將其指派給「Singleton.instance」來初始化該實例。這確保了對建構函數的後續呼叫將傳回相同的實例。
當使用新的 Singleton() 語法建立實例 1 和實例 2 時,這兩個變數都會引用 Singleton 類別的同一個實例。因此,當使用嚴格相等運算子比較實例 1 === 實例 2 時,其計算結果為 true。
工廠模式
工廠模式提供了一種建立物件而無需指定其特定類別的方法。它將物件建立邏輯封裝在一個單獨的工廠方法中,允許建立者和建立的物件之間的靈活性和解耦。
// Implementation example of the Factory Pattern
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
class CarFactory {
createCar(make, model) {
return new Car(make, model);
}
}
const factory = new CarFactory();
const myCar = factory.createCar("Tope", "Model 1");
在此範例中,使用 new CarFactory() 建立了一個 CarFactory 實例,然後使用參數「Tope」和「Model 1」在工廠上呼叫「createCar」方法。這將建立一個新的 Car 物件,其品牌為“Tope”,型號為“Model 1”,並分配給 myCar
變數。
建構函式模式
建構函式模式使用“new”關鍵字從建構函式建立物件。它允許您在建構函數中定義和初始化物件屬性。
// Implementation example of the Constructor Pattern
function Person(name, age) {
this.name = name;
this.age = age;
}
const tope = new Person("Tope", 24);
上面的程式碼定義了一個名為 Person 的建構函數,它帶有兩個參數:姓名和年齡。在函數內部,使用 this 關鍵字將名稱和年齡值指派給新建立的物件的對應屬性。
稍後,透過使用參數“Tope”和 24 呼叫 Person 函數來建立 Person 物件的新實例。這將建立一個新物件,其 name 屬性設為“Tope”,age 屬性設為 24,然後指派給變數top。這段程式碼的輸出是 Tope 持有一個物件,代表一個名為「Tope」、年齡為 24 歲的人。
原型模式
JavaScript 中的原型模式專注於透過複製或擴展現有物件作為原型來建立物件。它允許我們建立新實例而無需明確定義它們的類別。在此模式中,物件充當建立新物件的原型,從而實現繼承以及在多個物件之間共享屬性和方法。
// Prototype object
const carPrototype = {
wheels: 4,
startEngine() {
console.log("Engine started.");
},
stopEngine() {
console.log("Engine stopped.");
}
};
// Create new car instance using the prototype
const car1 = Object.create(carPrototype);
car1.make = "Toyota";
car1.model = "Camry";
// Create another car instance using the same prototype
const car2 = Object.create(carPrototype);
car2.make = "Honda";
car2.model = "Accord";
car1.startEngine(); // Output: "Engine started."
car2.stopEngine(); // Output: "Engine stopped."
在此範例中,汽車實例 car1 和 car2 是使用原型物件 carPrototype 建立的。 car1 的品牌為“Toyota”,型號為“Camry”,而 car2 的品牌為“Honda”,型號為“Accord”。當呼叫 car1.startEngine()
時,輸出“Engine started.”,當呼叫 car2.stopEngine()
時,輸出“Engine waiting.”。這示範如何利用原型物件在多個實例之間共用屬性和方法。
建造者模式
在建構器模式中,建構器類別或物件負責建構最終物件。它提供了一組方法來配置和設定正在建置的物件的屬性。建置過程通常涉及按特定順序呼叫這些方法來逐步建立物件。
class CarBuilder {
constructor() {
this.car = new Car();
}
setMake(make) {
this.car.make = make;
return this;
}
setModel(model) {
this.car.model = model;
return this;
}
setEngine(engine) {
this.car.engine = engine;
return this;
}
setWheels(wheels) {
this.car.wheels = wheels;
return this;
}
build() {
return this.car;
}
}
class Car {
constructor() {
this.make = "";
this.model = "";
this.engine = "";
this.wheels = 0;
}
displayInfo() {
console.log(`Make: ${this.make}, Model: ${this.model}, Engine: ${this.engine}, Wheels: ${this.wheels}`);
}
}
// Usage
const carBuilder = new CarBuilder();
const car = carBuilder.setMake("Toyota").setModel("Camry").setEngine("V6").setWheels(4).build();
car.displayInfo(); // Output: Make: Toyota, Model: Camry, Engine: V6, Wheels: 4
在此範例中,「CarBuilder」類別允許建構具有不同屬性的 Car 物件。透過呼叫setMake
、setModel
、setEngine
、setWheels
方法,設定Car物件的屬性。 build 方法完成建置並傳回完全建置的 Car 物件。 Car 類別代表一輛汽車,並包含一個「displayInfo」方法來記錄其詳細資訊。透過建立「carBuilder」實例並連結屬性設定方法,可以使用特定的品牌、型號、引擎和車輪值來建構汽車物件。呼叫“car.displayInfo()”顯示汽車的資訊。
模組模式
模組模式將相關的方法和屬性封裝到單一模組中,提供了一種乾淨的方式來組織和保護程式碼。它允許私有和公共成員,從而實現資訊隱藏並防止全域名稱空間污染。
const MyModule = (function() {
// Private members
let privateVariable = "I am private";
function privateMethod() {
console.log("This is a private method");
}
// Public members
return {
publicVariable: "I am public",
publicMethod() {
console.log("This is a public method");
// Accessing private members within the module
console.log(privateVariable);
privateMethod();
}
};
})();
// Usage
console.log(MyModule.publicVariable); // Output: "I am public"
MyModule.publicMethod(); // Output: "This is a public method" "I am private" "This is a private method"
在此範例中,程式碼使用立即呼叫的函數表達式來封裝私人和公共成員。該模組具有私有變數和方法,以及公共變數和方法。存取時,公共成員提供預期的輸出。此模式允許對封裝的私有成員進行受控存取,同時公開選定的公共成員。
結構模式著重於組織和組合物件以形成更大的結構。它們促進物件的組合,定義物件之間的關係並提供靈活的方法來操縱其結構。 JavaScript 中一些常用的結構模式包括:
裝飾模式
立面圖案
適配器
橋
合成的
裝飾器模式
裝飾器模式可讓您動態新增行為或修改物件的現有行為。它透過用一個或多個裝飾器包裝物件來增強物件的功能,而無需修改其結構。
// Implementation example of the Decorator Pattern
class Coffee {
getCost() {
return 1;
}
}
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
getCost() {
return this.coffee.getCost() + 0.5;
}
}
const myCoffee = new Coffee();
const coffeeWithMilk = new CoffeeDecorator(myCoffee);
console.log(coffeeWithMilk.getCost()); // Output: 1.5
在此範例中,「CoffeeDecorator」類別包裝了基本「Coffee」物件並新增了附加功能。它有一個「getCost」方法,透過將基礎咖啡的成本與 0.5 的附加成本相結合來計算總成本。
在使用部分,建立了「Coffee」類別的「myCoffee」實例。然後,實例化「CoffeeDecorator」類別的「coffeeWithMilk」實例,並將「myCoffee」作為參數傳遞。當呼叫“coffeeWithMilk.getCost()”時,它會返回咖啡的總成本以及裝飾器加入的成本,從而得到 1.5 的輸出。此範例說明了裝飾器模式如何透過動態新增或修改物件的屬性或方法來擴展物件的功能。
立面圖案
外觀模式為複雜子系統提供了一個簡化的接口,充當隱藏底層實現細節的前端接口。它透過提供高級接口,提供了一種與複雜系統互動的便捷方式。
// Implementation example of the Facade Pattern
class SubsystemA {
operationA() {
console.log("Subsystem A operation.");
}
}
class SubsystemB {
operationB() {
console.log("Subsystem B operation.");
}
}
class Facade {
constructor() {
this.subsystemA = new SubsystemA();
this.subsystemB = new SubsystemB();
}
operation() {
this.subsystemA.operationA();
this.subsystemB.operationB();
}
}
const facade = new Facade();
facade.operation(); // Output: "Subsystem A operation." "Subsystem B operation."
在此範例中,程式碼由三個類別組成:「SubsystemA」、「SubsystemB」和「Facade」。 SubsystemA
和 SubsystemB
類別代表獨立的子系統,並具有各自的 operationA
和 operationB
方法。 「Facade」類別作為一個簡化的接口,聚合了子系統的功能。
在使用部分,建立了“Facade”類別的“facade”實例。呼叫「facade.operation()」會觸發「SubsystemA」中的「operationA」和「SubsystemB」中的「operationB」的執行。結果,輸出顯示“子系統 A 操作”。接下來是「子系統 B 操作」。這展示了外觀模式如何提供統一且簡化的介面來與複雜的子系統交互,抽像出它們的複雜性並使它們更易於使用。
適配器模式
適配器模式是一種結構設計模式,它允許具有不相容介面的物件透過充當它們之間的橋樑來進行協作。它提供了一種將一個物件的介面轉換為客戶期望的另一個介面的方法。
// Implementation
class LegacyPrinter {
printLegacy(text) {
console.log(`Legacy Printing: ${text}`);
}
}
// Target interface
class Printer {
print(text) {}
}
// Adapter
class PrinterAdapter extends Printer {
constructor() {
super();
this.legacyPrinter = new LegacyPrinter();
}
print(text) {
this.legacyPrinter.printLegacy(text);
}
}
// Usage
const printer = new PrinterAdapter();
printer.print("Hello, World!"); // Output: "Legacy Printing: Hello, World!"
在此程式碼中,適配器模式用於彌合「LegacyPrinter」類別和所需的「Printer」介面之間的差距。 PrinterAdapter
擴展了 Printer
類,並在內部利用 LegacyPrinter
來適配 print
方法。當呼叫 printer.print("Hello, World!")` 時,它會有效地觸發舊版列印功能,並輸出「Legacy Printing: Hello, World!」。這展示了適配器模式如何透過提供標準化介面來整合不相容的元件。
橋樑圖案
橋接模式是一種結構設計模式,它將系統的抽象和實現分開,允許系統獨立發展。它透過使用介面或抽象類別在兩者之間引入了橋樑。下面是一個範例程式碼片段來說明橋接模式:
// Example
class Shape {
constructor(color) {
this.color = color;
}
draw() {}
}
// Concrete Abstractions
class Circle extends Shape {
draw() {
console.log(`Drawing a ${this.color} circle`);
}
}
class Square extends Shape {
draw() {
console.log(`Drawing a ${this.color} square`);
}
}
// Implementor
class Color {
getColor() {}
}
// Concrete Implementors
class RedColor extends Color {
getColor() {
return "red";
}
}
class BlueColor extends Color {
getColor() {
return "blue";
}
}
// Usage
const redCircle = new Circle(new RedColor());
redCircle.draw(); // Output: "Drawing a red circle"
const blueSquare = new Square(new BlueColor());
blueSquare.draw(); // Output: "Drawing a blue square"
在此範例中,我們有由 Shape 類別表示的抽象,它具有顏色屬性和繪製方法。具體抽象(圓形和方形)繼承自 Shape 類別並實現其特定的繪製行為。 「Implementor」由 Color 類別表示,該類別聲明了「getColor」方法。具體的「Implementors」、「RedColor」和「BlueColor」繼承自 Color 類別並提供各自的顏色實作。
在使用部分,我們建立具體抽象的實例,傳遞適當的具體實現者物件。這允許抽象化將與顏色相關的功能委託給實現者。當我們呼叫draw方法時,它會從Implementor存取顏色並相應地執行繪圖操作。
複合模式
組合模式是一種結構設計模式,可讓您統一處理單一物件和物件組合。它使您能夠建立層次結構,其中每個元素都可以被視為單個物件或物件集合。此模式使用通用介面來表示單一物件(葉節點)和組合(複合節點),允許客戶端與它們統一互動。
// Implementation
class Employee {
constructor(name) {
this.name = name;
}
print() {
console.log(`Employee: ${this.name}`);
}
}
// Composite
class Manager extends Employee {
constructor(name) {
super(name);
this.employees = [];
}
add(employee) {
this.employees.push(employee);
}
remove(employee) {
const index = this.employees.indexOf(employee);
if (index !== -1) {
this.employees.splice(index, 1);
}
}
print() {
console.log(`Manager: ${this.name}`);
for (const employee of this.employees) {
employee.print();
}
}
}
// Usage
const john = new Employee("John Doe");
const jane = new Employee("Jane Smith");
const mary = new Manager("Mary Johnson");
mary.add(john);
mary.add(jane);
const peter = new Employee("Peter Brown");
const bob = new Manager("Bob Williams");
bob.add(peter);
bob.add(mary);
bob.print();
在此範例中,我們有 Component 類別 Employee,它代表個別員工。 Composite 類 Manager 擴展了 Employee 類,並且可以包含員工的集合。它提供了在集合中新增和刪除員工的方法,並重寫 print 方法以顯示經理的姓名及其下的員工。
在使用部分,我們建立一個複合層次結構,其中 Manager 物件可以包含單一員工 (Employee) 和其他經理 (Manager)。我們將員工加入經理中,建構了一個層次結構。最後,我們呼叫頂級經理的 print 方法,該方法遞歸地列印層次結構,顯示經理及其各自的員工。
行為模式關注物件之間的互動和職責分配。它們為物件之間的通訊、協調和協作提供解決方案。以下是行為模式的類型。
觀察者模式
策略模式
命令模式
迭代器模式
調解者模式
觀察者模式
觀察者模式在物件之間建立一對多關係,其中多個觀察者會收到主體狀態變化的通知。它支援物件之間的鬆散耦合並促進事件驅動的通訊。
// Implementation example of the Observer Pattern
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notifyObservers() {
this.observers.forEach((observer) => observer.update());
}
}
class Observer {
update() {
console.log("Observer is notified of changes.");
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers(); // Output: "Observer is notified of changes." "Observer is notified of changes."
在此範例中,「Subject」類別表示一個主題,它維護觀察者清單並提供新增、刪除和通知觀察者的方法。 「Observer」類別透過其「update」方法定義觀察者的行為。在使用部分,建立了「Subject」類別的「subject」實例。也使用“addObserver”方法建立兩個“observer”實例並將其新增至主題。
當呼叫“subject.notifyObservers()”時,它會觸發每個觀察者的“update”方法。結果,輸出「觀察者收到更改通知」。被記錄兩次,顯示觀察者已被告知主題的變化。
策略模式
策略模式可讓您將可互換的演算法封裝在單獨的策略物件中。它支援在執行時動態選擇演算法,從而提高靈活性和可擴展性。
// Implementation example of the Strategy Pattern
class Context {
constructor(strategy) {
this.strategy = strategy;
}
executeStrategy() {
this.strategy.execute();
}
}
class ConcreteStrategyA {
execute() {
console.log("Strategy A is executed.");
}
}
class ConcreteStrategyB {
execute() {
console.log("Strategy B is executed.");
}
}
const contextA = new Context(new ConcreteStrategyA());
contextA.executeStrategy(); // Output: "Strategy A is executed."
const contextB = new Context(new ConcreteStrategyB());
contextB.executeStrategy(); // Output: "Strategy B is executed."
在此範例中,「Context」類別表示封裝不同策略的上下文,具有「strategy」屬性和「executeStrategy」方法。有兩個特定策略類,“ConcreteStrategyA”和“ConcreteStrategyB”,每個類別都有自己的“execute”方法來輸出特定訊息。
在使用部分,使用“ConcreteStrategyA”作為策略來建立“Context”類別的“contextA”實例。呼叫 contextA.executeStrategy()
會呼叫 ConcreteStrategyA
的 execute
方法,導致輸出「策略 A 已執行」。類似地,以「ConcreteStrategyB」為策略建立「contextB」實例,呼叫「contextB.executeStrategy()」會觸發「ConcreteStrategyB」的「execute」方法,從而輸出「策略 B 已執行」。這演示了策略模式如何透過將行為封裝在不同的策略物件中來允許在執行時動態選擇行為。
命令模式
命令模式將請求封裝為物件,允許您使用不同的請求對客戶端進行參數化、對請求進行排隊或記錄請求,並支援撤銷操作。它將請求的發送者與接收者解耦,從而促進鬆散耦合和靈活性。
// Implementation
class Receiver {
execute() {
console.log("Receiver executes the command.");
}
}
class Command {
constructor(receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.execute();
}
}
class Invoker {
setCommand(command) {
this.command = command;
}
executeCommand() {
this.command.execute();
}
}
const receiver = new Receiver();
const command = new Command(receiver);
const invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand(); // Output: "Receiver executes the command."
在此範例中,「Receiver」類別在呼叫時執行命令,「Command」類別封裝命令並將執行委託給接收者。 Invoker
類別設定並執行命令。在使用部分,建立了接收者、命令和呼叫者。此指令是為呼叫者設定的,呼叫「invoker.executeCommand()」會執行該指令,從而產生輸出「接收者執行該指令」。
迭代器模式
迭代器模式是一種行為設計模式,它提供了一種順序存取聚合物件的元素而不暴露其底層表示的方法。它允許您以統一的方式遍歷物件集合,而不管集合的具體實現如何。該模式將遍歷邏輯與集合分開,從而促進了一種乾淨而靈活的方法來迭代元素。
// Implementation
class Collection {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
createIterator() {}
}
// Concrete Aggregate
class ConcreteCollection extends Collection {
createIterator() {
return new ConcreteIterator(this);
}
}
// Iterator
class Iterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
hasNext() {}
next() {}
}
// Concrete Iterator
class ConcreteIterator extends Iterator {
hasNext() {
return this.index < this.collection.items.length;
}
next() {
return this.collection.items[this.index++];
}
}
// Usage
const collection = new ConcreteCollection();
collection.addItem("Item 1");
collection.addItem("Item 2");
collection.addItem("Item 3");
const iterator = collection.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
在此程式碼中,我們有由 Collection 類別表示的 Aggregate,它定義了用於建立迭代器物件的介面。具體聚合「ConcreteCollection」擴展了 Collection 類別並提供了迭代器建立的具體實作。
Iterator 由 Iterator 類別表示,它定義了存取和遍歷元素的介面。具體迭代器“ConcreteIterator”擴展了迭代器類別並提供了迭代邏輯的具體實作。在使用部分,我們建立一個 Concrete Aggregate 的實例“ConcreteCollection”,並向其中新增專案。然後我們使用 createIterator 方法建立一個迭代器。透過使用迭代器的“hasNext”和 next 方法,我們迭代集合併列印每個專案。
調解者模式
中介者模式透過引入充當協調物件之間互動的中心樞紐的中介者物件來簡化物件溝通。它封裝了通訊邏輯,並為物件提供了註冊、發送和接收訊息的方法。
// Implementation
class Mediator {
constructor() {
this.colleague1 = null;
this.colleague2 = null;
}
setColleague1(colleague) {
this.colleague1 = colleague;
}
setColleague2(colleague) {
this.colleague2 = colleague;
}
notifyColleague1(message) {
this.colleague1.receive(message);
}
notifyColleague2(message) {
this.colleague2.receive(message);
}
}
class Colleague {
constructor(mediator) {
this.mediator = mediator;
}
send(message) {
// Send a message to the mediator
this.mediator.notifyColleague2(message);
}
receive(message) {
console.log(`Received message: ${message}`);
}
}
// Usage
const mediator = new Mediator();
const colleague1 = new Colleague(mediator);
const colleague2 = new Colleague(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.send("Hello Colleague 2!"); // Output: "Received message: Hello Colleague 2!"
在此範例中,我們有一個 Mediator 類,它充當兩個 Colleague 物件之間的中介。中介者保存對同事的引用並提供在他們之間發送訊息的方法。
每個Colleague物件都有一個對中介者的引用,並且可以透過通知中介者來發送訊息。調解員又將訊息轉發給適當的同事。在這種情況下,同事 1 會向同事 2 發送訊息,後者接收並記錄該訊息。
我們探索了 JavaScript 中的一系列基本設計模式,包括建立模式、結構模式和行為模式。建立模式使我們能夠以靈活且高效的方式建立物件。結構模式有助於器官的靈活性和可擴展性。行為模式支援 JavaScript 物件之間的有效溝通和互動。透過利用這些設計模式,JavaScript 開發人員可以提高程式碼的可重複使用性、可維護性和整體系統效能。有了這些知識,我們就可以建立健壯且高效的 JavaScript 應用程式,以滿足現代軟體開發的需求。
原文出處:https://dev.to/topefasasi/js-design-patterns-a-comprehensive-guide-h3m