javascript-design-patterns-for-humans

javascript-design-patterns-for-humans

JavaScript设计模式实用指南与代码实例

该项目提供了简化的JavaScript设计模式学习方法。涵盖创建型、结构型和行为型模式,通过实例和代码演示阐释核心概念和应用。重点展示JavaScript中的设计模式实现,适合前端开发者学习参考。项目采用通俗易懂的方式解释复杂概念,帮助开发者更好地理解和应用设计模式。

设计模式软件开发面向对象编程代码重用架构设计Github开源项目

Design Patterns For Humans


<p align="center"> 🎉 Ultra-simplified explanation to design patterns! 🎉 </p> <p align="center"> A topic that can easily make anyone's mind wobble. Here I try to make them stick in to your mind (and maybe mine) by explaining them in the <i>simplest</i> way possible. <br/> Based on <a href="https://github.com/kamranahmedse/design-patterns-for-humans">"Design patterns for humans"</a> </p>

Follow me on twitter and check out my blog

🚀 Introduction

Design patterns are solutions to recurring problems guidelines on how to tackle certain problems. They are not classes, packages or libraries that you can plug into your application and wait for the magic to happen. These are, rather, guidelines on how to tackle certain problems in certain situations.

Design patterns solutions to recurring problems guidelines on how to tackle certain problems

Wikipedia describes them as

In software engineering, a software design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations.

⚠️ Be Careful

  • Design patterns are not a silver bullet to all your problems.
  • Do not try to force them bad things are supposed to happen, if done so. Keep in mind that design patterns are solutions to problems, not solutions finding problems so don't overthink.
  • If used in a correct place in a correct manner, they can prove to be a savior or else they can result in a horrible mess of a code.

🐢 Before you start

  • All design patterns have been implemented in ES6, the new version of javascript.
  • Since javascript does not have any implementation of interfaces, the examples here use implied interfaces, which means that as long as a class has attributes and methods that a particular interface is supposed to have, it is considered to implement that interface. To make it easier to tell the interface we are using, its information can be found in the comments of every example.

Types of Design Patterns

Creational Design Patterns

In plain words

Creational patterns are focused towards how to instantiate an object or group of related objects.

Wikipedia says

In software engineering, creational design patterns are design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation.

🏠 Simple Factory

Real world example

Consider, you are building a house and you need doors. It would be a mess if every time you need a door, you put on your carpenter clothes and start making a door in your house. Instead you get it made from a factory.

In plain words

Simple factory simply generates an instance for client without exposing any instantiation logic to the client

Wikipedia says

In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new".

Programmatic Example

First of all we have a door interface and the implementation

/* Door getWidth() getHeight() */ class WoodenDoor { constructor(width, height){ this.width = width this.height = height } getWidth(){ return this.width } getHeight(){ return this.height } }

Then we have our door factory that makes the door and returns it

const DoorFactory = { makeDoor : (width, height) => new WoodenDoor(width, height) }

And then it can be used as

const door = DoorFactory.makeDoor(100, 200) console.log('Width:', door.getWidth()) console.log('Height:', door.getHeight())

When to Use?

When creating an object is not just a few assignments and involves some logic, it makes sense to put it in a dedicated factory instead of repeating the same code everywhere.

🏭 Factory Method

Real world example

Consider the case of a hiring manager. It is impossible for one person to interview for each of the positions. Based on the job opening, she has to decide and delegate the interview steps to different people.

In plain words

It provides a way to delegate the instantiation logic to child classes.

Wikipedia says

In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.

Programmatic Example

Taking our hiring manager example above. First of all we have an interviewer interface and some implementations for it

/* Interviewer interface askQuestions() */ class Developer { askQuestions() { console.log('Asking about design patterns!') } } class CommunityExecutive { askQuestions() { console.log('Asking about community building') } }

Now let us create our HiringManager

class HiringManager { takeInterview() { const interviewer = this.makeInterviewer() interviewer.askQuestions() } }

Now any child can extend it and provide the required interviewer

class DevelopmentManager extends HiringManager { makeInterviewer() { return new Developer() } } class MarketingManager extends HiringManager { makeInterviewer() { return new CommunityExecutive() } }

and then it can be used as

const devManager = new DevelopmentManager() devManager.takeInterview() // Output: Asking about design patterns const marketingManager = new MarketingManager() marketingManager.takeInterview() // Output: Asking about community building.

When to use?

Useful when there is some generic processing in a class but the required sub-class is dynamically decided at runtime. Or putting it in other words, when the client doesn't know what exact sub-class it might need.

🔨 Abstract Factory

Real world example

Extending our door example from Simple Factory. Based on your needs you might get a wooden door from a wooden door shop, iron door from an iron shop or a PVC door from the relevant shop. Plus you might need a guy with different kind of specialities to fit the door, for example a carpenter for wooden door, welder for iron door etc. As you can see there is a dependency between the doors now, wooden door needs carpenter, iron door needs a welder etc.

In plain words

A factory of factories is a factory that groups the individual but related/dependent factories together without specifying their concrete classes.

Wikipedia says

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes

Programmatic Example

Translating the door example above. First of all we have our Door interface and some implementation for it

/* Door interface : getDescription() */ class WoodenDoor { getDescription() { console.log('I am a wooden door') } } class IronDoor { getDescription() { console.log('I am an iron door') } }

Then we have some fitting experts for each door type

/* DoorFittingExpert interface : getDescription() */ class Welder { getDescription() { console.log('I can only fit iron doors') } } class Carpenter { getDescription() { console.log('I can only fit wooden doors') } }

Now we have our abstract factory that would let us make family of related objects i.e. wooden door factory would create a wooden door and wooden door fitting expert and iron door factory would create an iron door and iron door fitting expert

/* DoorFactory interface : makeDoor() makeFittingExpert() */ // Wooden factory to return carpenter and wooden door class WoodenDoorFactory { makeDoor(){ return new WoodenDoor() } makeFittingExpert() { return new Carpenter() } } // Iron door factory to get iron door and the relevant fitting expert class IronDoorFactory { makeDoor(){ return new IronDoor() } makeFittingExpert() { return new Welder() } }

And then it can be used as

woodenFactory = new WoodenDoorFactory() door = woodenFactory.makeDoor() expert = woodenFactory.makeFittingExpert() door.getDescription() // Output: I am a wooden door expert.getDescription() // Output: I can only fit wooden doors // Same for Iron Factory ironFactory = new IronDoorFactory() door = ironFactory.makeDoor() expert = ironFactory.makeFittingExpert() door.getDescription() // Output: I am an iron door expert.getDescription() // Output: I can only fit iron doors

As you can see the wooden door factory has encapsulated the carpenter and the wooden door also iron door factory has encapsulated the iron door and welder. And thus it had helped us make sure that for each of the created door, we do not get a wrong fitting expert.

When to use?

When there are interrelated dependencies with not-that-simple creation logic involved

👷 Builder

Real world example

Imagine you are at Hardee's and you order a specific deal, lets say, "Big Hardee" and they hand it over to you without any questions this is the example of simple factory. But there are cases when the creation logic might involve more steps. For example you want a customized Subway deal, you have several options in how your burger is made e.g what bread do you want? what types of sauces would you like? What cheese would you want? etc. In such cases builder pattern comes to the rescue.

In plain words

Allows you to create different flavors of an object while avoiding constructor pollution. Useful when there could be several flavors of an object. Or when there are a lot of steps involved in creation of an object.

Wikipedia says

The builder pattern is an object creation software design pattern with the intentions of finding a solution to the telescoping constructor anti-pattern.

Having said that let me add a bit about what telescoping constructor anti-pattern is. At one point or the other we have all seen a constructor like below:

constructor(size, cheese = true, pepperoni = true, tomato = false, lettuce = true) { // ... }

As you can see the number of constructor parameters can quickly get out of hand and it might become difficult to understand the arrangement of parameters. Plus this parameter list could keep on growing if you would want to add more options in future. This is called telescoping constructor anti-pattern.

Programmatic Example

The sane alternative is to use the builder pattern. First of all we have our burger that we want to make

class Burger { constructor(builder) { this.size = builder.size this.cheeze = builder.cheeze || false this.pepperoni = builder.pepperoni || false this.lettuce = builder.lettuce || false this.tomato = builder.tomato || false } }

And then we have the builder

class BurgerBuilder { constructor(size) { this.size = size } addPepperoni() { this.pepperoni = true return this } addLettuce() { this.lettuce = true return this } addCheeze() { this.cheeze = true return this } addTomato() { this.tomato = true return this } build() { return new Burger(this) } }

And then it can be used as:

const burger = (new BurgerBuilder(14)) .addPepperoni() .addLettuce() .addTomato() .build()

Javascript specific tip : When you find that the number of arguments to a function or method are too many (normally any more than 2 arguments is considered too much), use a single object argument instead of multiple arguments. This serves two purposes :

  1. It makes your code look less cluttered, since there is only one argument.
  2. You don't have to worry about the order of arguments since arguments are now passed as named properties of the object.

For example :

const burger = new Burger({ size : 14, pepperoni : true, cheeze : false, lettuce : true, tomato : true })

instead of :

const burger = new Burger(14, true, false, true, true)

When to use?

When there could be several flavors of an object and to avoid the constructor telescoping. The key difference from the factory pattern is that factory pattern is to be used when the creation is a one step process while builder pattern is to be used when the creation is a multi step process.

Read more...

🐑 Prototype

Real world example

Remember dolly? The sheep that was cloned! Lets not get into the details but the key point here is that it is all about cloning

In plain words

Create object based on an existing object through cloning.

Wikipedia says

The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.

In short, it allows you to create a copy of an existing object and modify it to your needs, instead of going through the trouble of creating an object from scratch and setting it up.

Programmatic Example

First of all we have our Sheep that we want to clone

class Sheep { constructor(name, category = "Mountain Sheep") { this.name = name; this.category = category; } setName(name) { this.name = name; } getName() { console.log(this.name); } setCategory(category) { this.category = category; } getCategory() { console.log(this.category); } }

And then we have a SheepPrototype object that clones objects given a prototype object. Its constructor function accepts a prototype of type Sheep

class SheepPrototype { constructor(proto) { this.proto = proto; } clone() { return new Sheep(this.proto.name, this.proto.category); } }

Then it can be cloned like below

const originalSheep = new Sheep("Jolly"); originalSheep.getName(); // Jolly originalSheep.getCategory(); // Mountain Sheep // Clone and modify what is required const prototype = new SheepPrototype(originalSheep); const clonedSheep = prototype.clone(); clonedSheep.setName("Dolly"); clonedSheep.getName(); // Dolly clonedSheep.getCategory(); // Mountain sheep

This was the classical implementation of the Prototype pattern, but JavaScript can do this far more effectively using its built-in prototype facility.

When to use?

When an object is required that is similar to existing object or when the creation would be expensive as compared to cloning.

💍 Singleton

Real world example

There can only be one president of a country at a time. The same president has to be brought to action, whenever duty calls. President here is singleton.

In plain words

Ensures that only one object of a particular class is ever created.

Wikipedia says

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.

Singleton pattern is actually considered an anti-pattern and overuse of it should be avoided. It is not necessarily bad and could have some valid use-cases but should be used with caution because it introduces a global state in your application and change to it in one place could affect in the other areas and it could become pretty difficult to debug. The other bad thing about them is it makes your code tightly coupled plus it mocking the singleton could be difficult.

Programmatic Example

In javascript, singletons can be implemented using the module pattern. Private variables and functions are hidden in a function closure, and public methods are selectively exposed.

const president = (function(){ const presidentsPrivateInformation = 'Super private' const name = 'Turd Sandwich' const getName = () => name return { getName } }())

Here, presidentsPrivateInformation and name are kept private. However, name can be accessed with the exposed president.getName method.

president.getName() // Outputs 'Turd Sandwich' president.name // Outputs undefined president.presidentsPrivateInformation // Outputs undefined

Structural Design Patterns

In plain words

Structural patterns are mostly concerned with object composition or in other words how the entities can use each other. Or yet another explanation would be, they help in answering "How to build a software component?"

Wikipedia says

In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.

🔌 Adapter

Real world

编辑推荐精选

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

iTerms

iTerms

企业专属的AI法律顾问

iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。

SimilarWeb流量提升

SimilarWeb流量提升

稳定高效的流量提升解决方案,助力品牌曝光

稳定高效的流量提升解决方案,助力品牌曝光

Sora2视频免费生成

Sora2视频免费生成

最新版Sora2模型免费使用,一键生成无水印视频

最新版Sora2模型免费使用,一键生成无水印视频

下拉加载更多