搜尋
首頁web前端js教程JavaScript 設計模式指南

侯賽因阿里夫撰寫✏️

想像一下這樣的狀況:一群建築師想要設計一座摩天大樓。在設計階段,他們必須考慮許多因素,例如:

  • 建築風格-建築應該是野獸派、極簡主義還是其他風格?
  • 底座的寬度-需要多大的尺寸才能防止大風天倒塌?
  • 抵禦自然災害 - 根據該建築物的位置需要採取哪些預防性結構措施來防止地震、洪水等造成的損壞?

需要考慮的因素很多,但有一點是可以確定的:很可能已經有一份藍圖可以幫助建造這座摩天大樓。如果沒有共同的設計或計劃,這些架構師將不得不重新發明輪子,這可能會導致混亂和多重低效率。

類似地,在程式設計世界中,開發人員經常參考一組設計模式來幫助他們建立軟體,同時遵循乾淨的程式碼原則。此外,這些模式無所不在,從而讓程式設計師專注於交付新功能,而不是每次都重新發明輪子。

在本文中,您將了解一些常用的 JavaScript 設計模式,並且我們將一起建立小型 Node.js 專案來說明每種設計模式的用法。

軟體工程中的設計模式是什麼?

設計模式是預先製作的藍圖,開發人員可以對其進行自訂以解決編碼過程中的重複設計問題。要記住的一件重要的事情是,這些藍圖不是程式碼片段,而是應對即將到來的挑戰的一般概念。

設計模式有許多好處:

  • 經過嘗試和測試 - 它們解決了軟體設計中的無數問題。了解並應用程式碼中的模式很有用,因為這樣做可以幫助您使用物件導向設計的原則解決各種問題
  • 定義通用語言 — 設計模式可協助團隊以有效的方式溝通。例如,隊友可以說“我們應該使用工廠方法來解決這個問題”,每個人都會明白他的意思以及他們建議背後的動機

在本文中,我們將介紹三類設計模式:

  • 建立型 — 用於建立物件
  • 結構 - 組裝這些物件以形成一個工作結構
  • 行為 - 在這些物件之間分配職責

讓我們來看看這些設計模式的實際應用吧!

創意設計模式

顧名思義,建立模式包含各種幫助開發人員建立物件的方法。

工廠

工廠方法是一種建立物件的模式,可以更好地控制物件的建立。這種方法適用於我們希望將物件建立邏輯集中在一個地方的情況。

以下是一些範例程式碼,展示了此模式的實際效果:

//file name: factory-pattern.js
//use the factory JavaScript design pattern:
//Step 1: Create an interface for our object. In this case, we want to create a car
const createCar = ({ company, model, size }) => ({
//the properties of the car:
  company,
  model,
  size,
  //a function that prints out the car's properties:
  showDescription() {
    console.log(
      "The all new ",
      model,
      " is built by ",
      company,
      " and has an engine capacity of ",
      size,
      " CC "
    );
  },
});
//Use the 'createCar' interface to create a car
const challenger = createCar({
  company: "Dodge",
  model: "Challenger",
  size: 6162,
});
//print out this object's traits:
challenger.showDescription();

讓我們逐段分解這段程式碼:createCarCar

  • 每輛車都有三個屬性:公司、型號和尺寸。此外,我們也定義了一個 showDescription 函數,它將註銷物件的屬性。此外,請注意 createCar 方法演示了我們如何在記憶體中實例化物件時進行精細控制
  • 後來,我們使用 createCar 實例來初始化一個名為challenger的物件
  • 最後,在最後一行,我們在挑戰者實例上呼叫了 showDescription

讓我們來測試一下!我們應該期望程式註銷我們新建立的 Car 實例的詳細資訊:JavaScript design patterns guide

建設者

建構器方法讓我們可以使用逐步的物件建構來建構物件。因此,這種設計模式非常適合我們想要建立物件並僅應用必要功能的情況。因此,這提供了更大的靈活性。

這是使用建構器模式建立 Car 物件的程式碼區塊:

//builder-pattern.js
//Step 1: Create a class reperesentation for our toy car:
class Car {
  constructor({ model, company, size }) {
    this.model = model;
    this.company = company;
    this.size = size;
  }
}
//Use the 'builder' pattern to extend this class and add functions
//note that we have seperated these functions in their entities.
//this means that we have not defined these functions in the 'Car' definition.
Car.prototype.showDescription = function () {
  console.log(
    this.model +
      " is made by " +
      this.company +
      " and has an engine capacity of " +
      this.size +
      " CC "
  );
};
Car.prototype.reduceSize = function () {
  const size = this.size - 2; //function to reduce the engine size of the car.
  this.size = size;
};
const challenger = new Car({
  company: "Dodge",
  model: "Challenger",
  size: 6162,
});
//finally, print out the properties of the car before and after reducing the size:
challenger.showDescription();
console.log('reducing size...');
//reduce size of car twice:
challenger.reduceSize();
challenger.reduceSize();
challenger.showDescription();

這是我們在上面的程式碼區塊中所做的事情:

  • As a first step, we created a Car class which will help us instantiate objects. Notice that earlier in the factory pattern, we used a createCar function, but here we are using classes. This is because classes in JavaScript let developers construct objects in pieces. Or, in simpler words, to implement the JavaScript builder design pattern, we have to opt for the object-oriented paradigm
  • Afterwards, we used the prototype object to extend the Car class. Here, we created two functions — showDescription and reduceSize
  • Later on, we then created our Car instance, named it challenger, and then logged out its information
  • Finally, we invoked the reduceSize method on this object to decrement its size, and then we printed its properties once more

The expected output should be the properties of the challenger object before and after we reduced its size by four units: JavaScript design patterns guide   This confirms that our builder pattern implementation in JavaScript was successful!

Structural design patterns

Structural design patterns focus on how different components of our program work together.

Adapter

The adapter method allows objects with conflicting interfaces to work together. A great use case for this pattern is when we want to adapt old code to a new codebase without introducing breaking changes:

//adapter-pattern.js
//create an array with two fields: 
//'name' of a band and the number of 'sold' albums
const groupsWithSoldAlbums = [
  {
    name: "Twice",
    sold: 23,
  },
  { name: "Blackpink", sold: 23 },
  { name: "Aespa", sold: 40 },
  { name: "NewJeans", sold: 45 },
];
console.log("Before:");
console.log(groupsWithSoldAlbums);
//now we want to add this object to the 'groupsWithSoldAlbums' 
//problem: Our array can't accept the 'revenue' field
// we want to change this field to 'sold'
var illit = { name: "Illit", revenue: 300 };
//Solution: Create an 'adapter' to make both of these interfaces..
//..work with each other
const COST_PER_ALBUM = 30;
const convertToAlbumsSold = (group) => {
  //make a copy of the object and change its properties
  const tempGroup = { name: group.name, sold: 0 };
  tempGroup.sold = parseInt(group.revenue / COST_PER_ALBUM);
  //return this copy:
  return tempGroup;
};
//use our adapter to make a compatible copy of the 'illit' object:
illit = convertToAlbumsSold(illit);
//now that our interfaces are compatible, we can add this object to the array
groupsWithSoldAlbums.push(illit);
console.log("After:");
console.log(groupsWithSoldAlbums);

Here’s what’s happening in this snippet:

  • First, we created an array of objects called groupsWithSoldAlbums. Each object will have a name and sold property
  • We then made an illit object which had two properties — name and revenue. Here, we want to append this to the groupsWithSoldAlbums array. This might be an issue, since the array doesn’t accept a revenue property
  • To mitigate this problem, use the adapter method. The convertToAlbumsSold function will adjust the illit object so that it can be added to our array

When this code is run, we expect our illit object to be part of the groupsWithSoldAlbums list: JavaScript design patterns guide

Decorator

This design pattern lets you add new methods and properties to objects after creation. This is useful when we want to extend the capabilities of a component during runtime.

If you come from a React background, this is similar to using Higher Order Components. Here is a block of code that demonstrates the use of the JavaScript decorator design pattern:

//file name: decorator-pattern.js
//Step 1: Create an interface
class MusicArtist {
  constructor({ name, members }) {
    this.name = name;
    this.members = members;
  }
  displayMembers() {
    console.log(
      "Group name",
      this.name,
      " has",
      this.members.length,
      " members:"
    );
    this.members.map((item) => console.log(item));
  }
}
//Step 2: Create another interface that extends the functionality of MusicArtist
class PerformingArtist extends MusicArtist {
  constructor({ name, members, eventName, songName }) {
    super({ name, members });
    this.eventName = eventName;
    this.songName = songName;
  }
  perform() {
    console.log(
      this.name +
        " is now performing at " +
        this.eventName +
        " They will play their hit song " +
        this.songName
    );
  }
}
//create an instance of PerformingArtist and print out its properties:
const akmu = new PerformingArtist({
  name: "Akmu",
  members: ["Suhyun", "Chanhyuk"],
  eventName: "MNET",
  songName: "Hero",
});
akmu.displayMembers();
akmu.perform();

Let's explain what's happening here:

  • In the first step, we created a MusicArtist class which has two properties: name and members. It also has a displayMembers method, which will print out the name and the members of the current music band
  • Later on, we extended MusicArtist and created a child class called PerformingArtist. In addition to the properties of MusicArtist, the new class will have two more properties: eventName and songName. Furthermore, PerformingArtist also has a perform function, which will print out the name and the songName properties to the console
  • Afterwards, we created a PerformingArtist instance and named it akmu
  • Finally, we logged out the details of akmu and invoked the perform function

The output of the code should confirm that we successfully added new capabilities to our music band via the PerformingArtist class: JavaScript design patterns guide

Behavioral design patterns

This category focuses on how different components in a program communicate with each other.

Chain of Responsibility

The Chain of Responsibility design pattern allows for passing requests through a chain of components. When the program receives a request, components in the chain either handle it or pass it on until the program finds a suitable handler.

Here’s an illustration that explains this design pattern: JavaScript design patterns guide The bucket, or request, is passed down the chain of components until a capable component is found. When a suitable component is found, it will process the request. Source: Refactoring Guru.[/caption] The best use for this pattern is a chain of Express middleware functions, where a function would either process an incoming request or pass it to the next function via the next() method:

//Real-world situation: Event management of a concert
//implement COR JavaScript design pattern:
//Step 1: Create a class that will process a request
class Leader {
  constructor(responsibility, name) {
    this.responsibility = responsibility;
    this.name = name;
  }
  //the 'setNext' function will pass the request to the next component in the chain.
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  handle(responsibility) {
  //switch to the next handler and throw an error message:
    if (this.nextHandler) {
      console.log(this.name + " cannot handle operation: " + responsibility);
      return this.nextHandler.handle(responsibility);
    }
    return false;
  }
}
//create two components to handle certain requests of a concert
//first component: Handle the lighting of the concert:
class LightsEngineerLead extends Leader {
  constructor(name) {
    super("Light management", name);
  }
  handle(responsibility) {
  //if 'LightsEngineerLead' gets the responsibility(request) to handle lights,
  //then they will handle it
    if (responsibility == "Lights") {
      console.log("The lights are now being handled by ", this.name);
      return;
    }
    //otherwise, pass it to the next component.
    return super.handle(responsibility);
  }
}

//second component: Handle the sound management of the event:
class SoundEngineerLead extends Leader {
  constructor(name) {
    super("Sound management", name);
  }
  handle(responsibility) {
  //if 'SoundEngineerLead' gets the responsibility to handle sounds,
  // they will handle it
    if (responsibility == "Sound") {
      console.log("The sound stage is now being handled by ", this.name);
      return;
    }
    //otherwise, forward this request down the chain:
    return super.handle(responsibility);
  }
}
//create two instances to handle the lighting and sounds of an event:
const minji = new LightsEngineerLead("Minji");
const danielle = new SoundEngineerLead("Danielle");
//set 'danielle' to be the next handler component in the chain.
minji.setNext(danielle);
//ask Minji to handle the Sound and Lights:
//since Minji can't handle Sound Management, 
// we expect this request to be forwarded 
minji.handle("Sound");
//Minji can handle Lights, so we expect it to be processed
minji.handle("Lights");

In the above code, we’ve modeled a situation at a music concert. Here, we want different people to handle different responsibilities. If a person cannot handle a certain task, it’s delegated to the next person in the list.

Initially, we declared a Leader base class with two properties:

  • responsibility — the kind of task the leader can handle
  • name — the name of the handler

Additionally, each Leader will have two functions:

  • setNext: As the name suggests, this function will add a Leader to the responsibility chain
  • handle: The function will check if the current Leader can process a certain responsibility; otherwise, it will forward that responsibility to the next person via the setNext method

Next, we created two child classes called LightsEngineerLead (responsible for lighting), and SoundEngineerLead (handles audio). Later on, we initialized two objects — minji and danielle. We used the setNext function to set danielle as the next handler in the responsibility chain.

Lastly, we asked minji to handle Sound and Lights.

When the code is run, we expect minji to attempt at processing our Sound and Light responsibilities. Since minji is not an audio engineer, it should hand over Sound to a capable handler. In this case, it is danielle: JavaScript design patterns guide

Strategy

The strategy method lets you define a collection of algorithms and swap between them during runtime. This pattern is useful for navigation apps. These apps can leverage this pattern to switch between routes for different user types (cycling, driving, or running):

This code block demonstrates the strategy design pattern in JavaScript code:

//situation: Build a calculator app that executes an operation between 2 numbers.
//depending on the user input, change between division and modulus operations

class CalculationStrategy {
  performExecution(a, b) {}
}
//create an algorithm for division
class DivisionStrategy extends CalculationStrategy {
  performExecution(a, b) {
    return a / b;
  }
}
//create another algorithm for performing modulus
class ModuloStrategy extends CalculationStrategy {
  performExecution(a, b) {
    return a % b;
  }
}
//this class will help the program switch between our algorithms:
class StrategyManager {
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  executeStrategy(a, b) {
    return this.strategy.performExecution(a, b);
  }
}

const moduloOperation = new ModuloStrategy();
const divisionOp = new DivisionStrategy();
const strategyManager = new StrategyManager();
//use the division algorithm to divide two numbers:
strategyManager.setStrategy(divisionOp);
var result = strategyManager.executeStrategy(20, 4);
console.log("Result is: ", result);
//switch to the modulus strategy to perform modulus:
strategyManager.setStrategy(moduloOperation);
result = strategyManager.executeStrategy(20, 4);
console.log("Result of modulo is ", result);

Here’s what we did in the above block:

  • First we created a base CalculationStrategy abstract class which will process two numbers — a and b
  • We then defined two child classes — DivisionStrategy and ModuloStrategy. These two classes consist of division and modulo algorithms and return the output
  • Next, we declared a StrategyManager class which will let the program alternate between different algorithms
  • In the end, we used our DivisionStrategy and ModuloStrategy algorithms to process two numbers and return its output. To switch between these strategies, the strategyManager instance was used

When we execute this program, the expected output is strategyManager first using DivisionStrategy to divide two numbers and then switching to ModuloStrategy to return the modulo of those inputs: JavaScript design patterns guide

Conclusion

In this article, we learned about what design patterns are, and why they are useful in the software development industry. Furthermore, we also learned about different categories of JavaScript design patterns and implemented them in code.


LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.

JavaScript design patterns guide

LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

以上是JavaScript 設計模式指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具