搜索
首页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
从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

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广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

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尊渡假赌尊渡假赌尊渡假赌

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境