Gang of Four Design Patterns
- Authors
Updated at 6/14/2024
In the sake of completition, I didn't want to cover here each one GoF design pattern. I wanted to create something like a flashcards list where u can read the most crucial and packed information about the most often used (in my opinion) design patterns. That is not a blog post for beginners, rather than for mid/seniors.
Singleton (creational)
Restricts object creation for a class to only one instance. Simple JS object can be considered as a Singleton.
Prototype (creational)
It’s a fancy word for clone. It’s because the prototype design patterns means that we create a new object based on the old one. So we get a flat prototype chain, like: ObjectA -> ObjectB -> ObjectC
. Where the ObjectC will have all functionalities of ObjectA and ObjectB. It means that the next object in the prototype chain clones the previous object.
const zoombie = {
eatBrains() {
return 'yumm'
},
}
const chad = Object.create(zoombie, { name: { value: 'Chad' } })
console.log(chad)
// {name: 'Chad'} name: "Chad"[[Prototype]]: ObjecteatBrains: ƒ eatBrains()
You can check the prototype of an object by: Object.getPrototypeOf(object)
method.
Builder (creational)
We create objects in steps instead of all at once at the constructor.
const myLunch = new HotDog('gluten free')
myLunch.addKetchup().addMustard().addKraut()
// Instead of:
const myLunch = new HotDog('gluten free', true, false, true, true)
Factory (creational)
Creates an object based on some specific data, like:
class ButtonFactory {
createButton(os: string) {
if (os === 'Android') return new AndroidButton()
else return new IOSButton()
}
}
// Instead of:
const button = os === 'Andoid' ? new AndroidButton() : new IOSButton()
// You just need to do:
const button = factory.createButton(os)
Abstract Factory (creational)
The purpose of the Abstract Factory is to provide an interface for creating families of related objects, without specifying concrete classes. So basically, an Abstract Factory uses multiple Factories to create a whole family, not just a one class.
Difference between AbstractFactory and Factory design patterns:
- Factory Method is used to create one product only but Abstract Factory is about creating families of related or dependent products.
- Factory Method pattern exposes a method to the client for creating the object whereas in the case of Abstract Factory they expose a family of related objects which may consist of these Factory methods.
- Factory Method pattern hides the construction of a single object whereas Abstract Factory hides the construction of a family of related objects.
Adapter (structural)
Allows classes with incompatible interfaces to work together by wrapping its own interface around that of an already existing class. In the image example, the adapter would translate xml to json and provide a proper interface.
Facade (structural)
Provides an interface which shields clients from complex functionality in one or more subsystems. So basically, it’s a simplified interface to a large body of code.
const client = new House()
client.turnOnSystems()
client.shutDown()
Client doesn’t need to know how to turn on the heating, turn on lights etc. The client just runs turnOnSystems()
, and that method does all the job (turning all systems in the house).
The differences between Adapter and Facade patterns
Adapter and Facade are both wrappers; but they are different kinds of wrappers. The intent of Facade is to produce a simpler interface, and the intent of Adapter is to design a new one interface to make cooperation between objects possible.
Decorator (structural)
Dynamically adds/overrides behavior in an existing method of an object. It only extends the one specified object, not all objects created by a specific class.
Composite (structural)
Is used where we need to treat a group of objects in a similar way as a single object. Composite pattern composes objects in terms of a tree structure to represent part as well as whole hierarchy.
Using the Composite pattern makes sense only when the core model of your app can be represented as a tree.
Bridge (structural)
Lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other.
Is used mainly for implementing platform independence features. For instance, we are creating libraries for multiple environments (browsers, mobile apps etc). So we can separate implementation from abstraction. So instead of manual handling differences between platforms we simply write abstraction (eg. System class) with an implementation chosen at build time providing specific tools for mobile apps or for desktop etc.
Proxy (structural)
It’s a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a file, or some other resource that is expensive or impossible to duplicate.
Use of the proxy can simply be forwarding to the real object, or can provide additional logic, for example: caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked.
Chain of responsibility (behavioral)
Provides a chain of loosely coupled objects one of which can satisfy a request. This pattern is essentially a linear search for an object that can handle a particular request.
An example of a chain-of-responsibility is event-bubbling in which an event propagates through a series of nested controls one of which may choose to handle the event.
const sum1 = new CumulativeSum()
console.log(sum1.add(10).add(2).add(50).sum) // 62
add(value) {
this.sum += value;
return this;
}
Mediator (behavioral)
Provides central authority over a group of objects by encapsulating how these objects interact. This model is useful for scenarios where there is a need to manage complex conditions in which every object is aware of any state change in any other object in the group. Also, the Mediator pattern prevents direct connections between objects.
Template method (behavioral)
Defines a skeleton of an algorithm and let subclasses override the steps without changing the overall algorithm’s structure.
Code example: https://refactoring.guru/design-patterns/template-method/typescript/example#example-0--index-ts
Observer (behavioral)
It's one to many relationship where the Subject is being observed by Observers. When Subject changes, all observers automatically adapt to that change. An example is React component where component observes state and changes after it's changed. Also the example could be a RxJS library.
Visitor (behavioral)
The Visitor pattern defines a new operation to a collection of objects without changing the objects themselves.
Visitors are useful when building extensibility in a library or framework. If the objects in your project provide a 'visit' method that accepts a Visitor object which can make changes to the receiving object then you are providing an easy way for clients to implement future extensions.
Memento (behavioral)
Is about keeping a previous state of the object, which allows it to restore the object to that previous state.
Command (behavioral)
The Command pattern aims to encapsulate method invocation, requests, or operations into a single object and gives us the ability to both parameterize and pass method calls around that can be executed at our discretion. For example: Redux actions.
State (behavioral)
The State pattern provides state-specific logic to a limited set of objects in which each object represents a particular state. This is best explained with an example. The State pattern example could be React.useState.
Strategy (behavioral)
The Strategy pattern encapsulates alternative algorithms (or strategies) for a particular task. It allows a method to be swapped out at runtime by any other method (strategy) without the client realizing it. Essentially, Strategy is a group of algorithms that are interchangeable.
For example, depending on the list of elements choose a more suitable sort algorithm.