The author is Joey, Ant Financial · Data Experience Technology team

preface

Our team’s work is to implement web tools as single-page applications. Tens of thousands to tens of thousands of lines of front-end code are managed, and the project cycle is several years long.

Managing front-end code of this magnitude well and keeping it fresh through the iterations was a challenge.

A well-run project, in addition to a good architecture, also needs a good design of each functional module, learning the design pattern, is to be able to skillfully design new functions and reconstruct the existing code.

I read a lot on the Internet, saying that learning design patterns is not useful, some patterns are outdated, you can work without learning, learning is easy to over design.

I think the understanding of things is a process of “learning – understanding – breakthrough”. When you do not understand, learn first. When there is a difference between what you have learned and practical experience, you can understand it by thinking about it. When you understand the principle, you can not stick to what you have learned so that you can use it flexibly according to your own scenes. Overdesign is clearly a matter of learning and understanding.

The same is true of design patterns. The 23 design patterns listed in “Design Patterns” are not all of them, and the use of patterns is often not so clear. It is often a mixture of multiple patterns. Learning design patterns is like learning Taijiquan by Zhang Wuji in “The Story of Leaning on Heaven and Killing Dragons”. Learn the moves first, play them several times, and finally forget the moves. 23 kinds of design modes are just tricks. The purpose of our study is to improve our design level and achieve the “Mahayana” realm that can be combined with the scene without being stuck to the tricks.

In the process of learning design patterns, I found that the demo code of the original book of gang of 4 is C++, while the demo of the online design patterns article is mostly Java. Therefore, the combination of front-end JS language characteristics, sorted out a demo of each mode, convenient for students interested in learning design mode to understand, common progress.

Scene description

Suppose we want to implement a maze, the original code looks like this:

function createMazeDemo() {
  const maze = new Maze();
  
  const r1 = new Room(1);
  const r2 = new Room(2);
  const door = new Door(r1, r2);

  maze.addRoom(r1);
  maze.addRoom(r2);
  r1.setSide('east', new Wall());
  r1.setSide('west', door);
  
  return maze;
}

createMazeDemo();
Copy the code

We’ve already implemented a maze, and a new requirement comes in that everything in the maze is bewitched, but the existing layout needs to be reused (all component classes like Room, Wall, and Door need to be replaced with new classes).

As hard coding isn’t flexible enough, how can you adapt the createMaze method to make it easy to create mazes with new types of objects?

General concept definition

  • System: the entire program generated content, such as the maze is a system;
  • Products: The objects that constitute the system, such as the doors, rooms and walls of the maze, are products respectively;

Abstract Factory

define

Provides an interface for creating a series of related or interdependent objects without specifying their concrete classes.

structure

The abstract factory pattern contains the following roles:

  • AbstractFactory: AbstractFactory
  • ConcreteFactory: ConcreteFactory
  • AbstractProduct: AbstractProduct
  • Product: Specific Product

The sample

// Maze {addRoom(room: room): void {}} class Maze {addRoom(room: room): void {}} number) { }setSide (direction: string, content: Room | Wall) : void {}} / / Door is the base class for class feel {constructor (roo1: Room, room2: Room) {}} // The base class of the maze factory, which defines the interface and default implementation of each component of the maze. // Subclasses can duplicate the implementation of the interface and return different concrete class objects. class MazeFactory { makeMaze(): Maze {return new Maze();
  }
  makeWall(): Wall {
    return new Wall();
  }
  makeRoom(roomId: number): Room {
    return new Room(roomId);
  }
  makeDoor(room1: Room, room2: Room): Door {
    returnnew Door(room1, room2); }} // Create a maze by passing in the factory object and calling the factory interface method, // Since the factory interface is the same, passing in different factory objects can create different sets of concrete productsfunction createMazeDemo(factory: MazeFactory): Maze {
  const maze = factory.makeMaze();
  const r1 = factory.makeRoom(1);
  const r2 = factory.makeRoom(2);
  const door = factory.makeDoor(r1, r2);

  maze.addRoom(r1);
  maze.addRoom(r2);
  
  r1.setSide('east', factory.makeWall());
  r1.setSide('west', door);
  
  returnmaze; } // standardSeries factory object, each factory product is standard const standardSeries = new MazeFactory(); // create the standard maze createMazeDemo(standardSeries); Class MagicRoom extends Room {... class MagicRoom extends Room {... } // Class MagicDoor extends Door {... Class MagicMazeFactory extends MazeFactory {makeRoom(roomId: number): Room {class MagicMazeFactory extends MazeFactory {return new MagicRoom(roomId);
  }
  makeDoor(room1: Room, room2: Room): Door {
    returnnew MagicDoor(room1, room2); }... } // Const magicSeries = new MagicMazeFactory(); createMazeDemo(magicSeries);Copy the code

Applicable scenario

  • A series of related product objects. For example, magical rooms and doors belong to magical maze components and have a certain correlation;
  • The system is configured by one of multiple product families. For example, when the user has a variety of maze styles to choose, when choosing black style, all the components of the maze should be black style components;
  • The system should be independent of specific product classes. For example, when writing maze program, do not need to care about which specific room class is used, only need to know the interface of the room base class can operate the room. The room classes returned by various maze factories are inherited from the room base class, the interface is consistent, even if the ordinary room is changed into a magic room, there is no need to operate the room code, such as the layout of the maze code, care about change.

advantages

  • Concrete classes are separated. If the maze to be changed into a magic maze, maze layout part of the codecreateMazeDemoNo modification is required;
  • Easy to exchange product line. When a maze is changed to a magic maze, you just need to be rightcreateMazeDemoJust pass in a new factory object;
  • Conducive to product consistency. The same series of products, are relatively high correlation. Specific objects, such as the magic maze, are magical.

disadvantages

  • Difficult to support new kinds of products. This is because the interface of the abstract factory identifies the set of products that can be created, and to support the new kind of product requires extending the factory interface, which will involve changes to the abstract factory class and all of its subclasses.

Related patterns

  • Abstract factory patterns are usually implemented using factory methods, but can also be implemented using prototype patterns.
  • A specific factory is usually a singleton pattern.

The Builder

define

Separating the construction of a complex object from its representation allows the same construction process to create different representations.

structure

The Builder mode contains the following characters:

  • -Blair: You’re an abstract Builder
  • -Serena: ConcreteBuilder
  • The Director
  • Product: Product role

The sample

import { Maze, Wall, Room, Door } from './common'; Class MazeBuilder {buildMaze(): void {} buildWall(roomId: number, direction: string): void {} buildRoom(roomId: number): void {} buildDoor(roomId1: number, roomId2: number): void {} getCommonWall(roomId1: number, roomId2: number): Wall {return new Wall(); };
  getMaze(): Maze|null { returnnull; }} // The process of creating a maze // Compared to the original code, the builder mode only needs to declare the construction process, but does not need to know all the information about each component used in the construction process // For example, when building a door, only needs to declare a door, // Builder mode takes the entire maze out of the Builder object only after the maze has been completely built, so that it is a good reflection of the entire building processfunction createMaze(builder: MazeBuilder) {
  builder.buildMaze();
  builder.buildRoom(1);
  builder.buildRoom(2);
  builder.buildDoor(1, 2);
  
  returnbuilder.getMaze(); } class StandardMazeBuilder extends MazeBuilder {currentMaze: Maze;constructor() {
    super();
    this.currentMaze = new Maze();
  }
  getMaze(): Maze|null {
    return this.currentMaze;
  }
  buildRoom(roomId: number): void {
    if (this.currentMaze) {
      const room = new Room(roomId);
      this.currentMaze.addRoom(room);

      room.setSide('north', new Wall());
      room.setSide('south', new Wall());
      room.setSide('east', new Wall());
      room.setSide('west', new Wall()); } } buildDoor(roomId1: number, roomId2: number): void { const r1 = this.currentMaze.getRoom(roomId1); const r2 = this.currentMaze.getRoom(roomId2); const door = new Door(r1, r2); r1.setSide(this.getCommonWall(roomId1, roomId2), door); r2.setSide(this.getCommonWall(roomId2, roomId1), door); } // Build a standard maze const standardBuilder = new StandardMazeBuilder(); createMaze(standardBuilder); /** * Builders can also not build concrete components at all, but just count the construction process. */ // Count data structure declaration interface Count {rooms: number; doors: number; } class CountingMazeBuilder extends MazeBuilder {doors = 0; rooms = 0; buildRoom(): void { this.rooms += 1; } buildDoor(): void { this.doors += 1; } getCounts(): Count {return {
      rooms: this.rooms,
      doors: this.doors,
    };
  }
}

const countBuilder = new CountingMazeBuilder();
createMaze(countBuilder);
countBuilder.getCounts();
Copy the code

Applicable scenario

  • When algorithms that create complex objects should be independent of the components of the object and how they are assembled. In the above example, the process of building a maze is analogous to an algorithm, and each method of generating artifacts contains a way of generating concrete objects and assembling them onto the maze. The same build process can accommodate many different builders, while the same builder can support many different build processes, which are independent of each other.
  • When the construction process must allow different representations of the object being constructed. The construction process is the same, but different assembly methods need to be supported. In the above example, the same construction process needs to support two different scenarios, one scenario needs to build a real maze, and the other scenario only needs to count how many houses and doors are built during the construction process.

advantages

  • You can change the internal representation of a product. Interfaces can hide the presentation and internal structure of a product, as well as the assembly process of the product. Because the product is constructed through an abstract interface, all you have to do to change the internal representation of the product is define a new builder.
  • Separate construction code from presentation code. The Builder pattern improves the modularity of objects by encapsulating how a complex object is created and represented. The customer does not need to know all the information about the classes that define the internal structure of the product.
  • Finer control of the construction process is possible. The Builder pattern only retrieves the product from the builder when it is complete, so the Builder pattern reflects the construction process of the product better than other builder patterns.

Related patterns

  • The Abstract factory is similar to the Builder pattern in that it can also create complex objects. The main difference is that the Builder pattern focuses on constructing a complex object step by step. Abstract factories focus on multiple families of product objects. The builder returns the product in the last step, whereas for the abstract factory, the product returns immediately.
  • Composite patterns are typically generated using builder patterns.

Factory Method

define

Define an interface for creating objects and let subclasses decide which class to instantiate. The factory method delays the instantiation of a class to its subclasses.

structure

The factory method pattern contains the following roles:

  • Product: Abstract Product
  • Concreteproducts: Concrete products
  • Factory: Abstract Factory
  • ConcreteFactory: ConcreteFactory

The sample

The subclass decides to instantiate the specific product

import { Maze, Wall, Room, Door } from './common'; // createMaze(): Maze {const Maze = this.makemaze (); const r1 = this.makeRoom(1); const r2 = this.makeRoom(2); const door = this.makeDoor(r1, r2); maze.addRoom(r1); maze.addRoom(r2); r1.setSide('north', this.makeWall());
    r1.setSide('east', door);

    returnmaze; } // The most important thing about the factory method is to define the interface that returns the product. Although the default implementation is provided, it is possible to provide only the interface and let subclasses implement makeMaze(): Maze {return new Maze(); }
  makeRoom(roomId: number): Room { return new Room(roomId); }
  makeWall(): Wall { return new Wall(); }
  makeDoor(room1: Room, room2: Room): Door { returnnew Door(room1, room2); } // create a normal mazeGame const mazeGame = new mazeGame (); mazeGame.createMaze(); Class BombedWall extends Wall {... } // Class RoomWithABomb extends Room {... } class BombedMazeGame extends MazeGame extends MazeGame {class BombedMazeGame extends MazeGame {returnnew BombedWall(); MakeRoom (roomId: number): Room {returnnew RoomWithABomb(roomId); Const bombedMazeGame = new bombedMazeGame (); bombedMazeGame.createMaze();Copy the code

Parametric chemical plant method

class Creator {
  createProduct(type: string): Product {
    if (type= = ='normal') return new NormalProduct();
    if (type= = ='black) return new BlackProduct(); return new DefaultProduct(); } // Subclasses can easily extend or change the product returned by the factory method class MyCreator extends Creator {createProduct(type: string): Product {// change the Product if (type === 'normal) returnnew MyNormalProduct(); // Extend new productsif (type= = ='white') returnnew WhiteProduct(); // Notice that the last thing to do with this operation is to call the parent's 'createProduct', because subclasses are only valid for certain typestypeIs handled differently from the parent class for the othertypeNot interested inreturn Creator.prototype.createProduct.call(this, type); }}Copy the code

Applicable scenario

  • When a class does not know the class of the object it must create.
  • When a class wants its subclasses to specify the objects it creates.
  • When a class delegates the responsibility of creating an object to one of multiple help subclasses, and you want to localize which help subclass is the agent. This is the scenario for the parametric chemical plant method, which localizes the process of specifying concrete classes through parameters in the factory method.

advantages

  • Creating objects inside a class using factory methods is often more flexible than creating them directly. Compared with thecreateMazeMethod to create objects directlyconst r1 = new Room(1);By factory methodconst r1 = this.makeRoom(1), can be overridden in a subclassmakeRoomMethods to instantiate different rooms to be more flexible in response to changing needs.

Related patterns

  • Abstract factories are often implemented using factory methods.
  • Factory methods are usually invoked in the template method pattern.
  • The stereotype pattern does not require the creation of subclasses, but generally requires an initialization operation for the product class. The factory method does not require such operations.

Prototype

define

Specify what kind of objects to create with prototype instances, and create new objects by reusing these prototypes.

The sample

In other languages, the stereotype pattern reduces the overhead of class definition and instantiation by copying an object and then modifying the properties of the new object.

However, js naturally supports Prototype, so the implementation of prototype is somewhat different from other class inheritance languages. There is no need to provide clone method through objects to realize the model pattern.

import { Maze, Wall, Room, Door } from './common'; interface Prototype { prototype? : any; } // Return an object based on the stereotypefunctiongetNewInstance(prototype: Prototype, ... args: any[]): Wall|Maze|Room|Door { const proto = Object.create(prototype); const Kls = class {}; Kls.prototype = proto;returnnew Kls(... args); Class MazeFactory {makeWall(): Wall {class MazeFactory (): Wall {return new Wall(); }
  makeDoor(r1: Room, r2: Room): Door { return new Door(r1, r2); }
  makeRoom(id: number): Room { return new Room(id); }
  makeMaze(): Maze { returnnew Maze(); Class MazePrototypeFactory extends MazeFactory {mazePrototype: Prototype; wallPrototype: Prototype; roomPrototype: Prototype; doorPrototype: Prototype; constructor(mazePrototype: Prototype, wallPrototype: Prototype, roomPrototype: Prototype, doorPrototype: Prototype) { super(); this.mazePrototype = mazePrototype; this.wallPrototype = wallPrototype; this.roomPrototype = roomPrototype; this.doorPrototype = doorPrototype; }makeMaze() {
    return getNewInstance(this.mazePrototype);
  }
  makeRoom(id: number) {
    return getNewInstance(this.roomPrototype, id);
  }
  makeWall() {
    return getNewInstance(this.wallPrototype);
  }
  makeDoor(r1: Room, r2: Room): Door {
    const door = getNewInstance(this.doorPrototype, r1, r2);
    returndoor; }} // The process of creating a mazefunction createMaze(factory: MazeFactory): Maze {
  const maze = factory.makeMaze();
  const r1 = factory.makeRoom(1);
  const r2 = factory.makeRoom(2);
  const door = factory.makeDoor(r1, r2);

  maze.addRoom(r1);
  maze.addRoom(r2);
  
  r1.setSide('east', factory.makeWall());
  r1.setSide('west', door);
  
  returnmaze; } const mazePrototype = {... }; const wallPrototype = {... }; const roomPrototype = {... }; const doorPrototype = {... }; Const simpleMazeFactory = new MazePrototypeFactory(mazePrototype, wallPrototype, roomPrototype, doorPrototype); createMaze(simpleMazeFactory); Const bombedWallPrototype = {... }; const roomWithABombPrototype = {... }; Const bombedMazeFactory = new MazePrototypeFactory(mazePrototype, bombedWallPrototype, mazePrototype) roomWithABombPrototype, doorPrototype); createMaze(bombedMazeFactory);Copy the code

Applicable scenario

  • The class to be instantiated is specified at runtime. For example, after taking a prototype through dynamic loading, the application can create an instance directly from the prototype during running, instead of using a predefined classnewOut of the object. Or if the class needs to change dynamically depending on the runtime environment, you can modify the prototype to produce different objects.

advantages

  • Add products at runtime. Because new kinds of objects can be generated dynamically from prototypes at run time.
  • Reduce subclass construction and reduce the number of classes used in the system.
  • Dynamically configure applications with classes. Load classes dynamically at run time.

Related patterns

  • The prototype pattern and the abstract factory pattern compete with each other in some ways. But they can also be used together. Abstract factories can store a collection of stereotypes and return product objects.
  • Designs that make heavy use of composite and decorative patterns can often benefit from prototyping patterns as well.

Singleton

define

Ensure that a class has only one instance and provide a global access point to access it.

structure

The singleton pattern contains the following roles:

  • Singleton: Singleton

The sample

Simple singleton

Class MazeFactory {// Make constructor private to prevent the singleton from being private by generating multiple objects through the new classconstructor() {} static instance: MazeFactory; Static getInstance(): MazeFactory {static getInstance(): MazeFactory {if(! MazeFactory.instance) { MazeFactory.instance = new MazeFactory(); }returnMazeFactory.instance; }}Copy the code

Select the maze factory to instantiate based on the parameters

class BombedMazeFactory extends MazeFactory { ... } class AdvancedMazeFactory {// Make constructor private to prevent generating multiple objects from the new class and breaking the singleton privateconstructor() {}
  static instance: MazeFactory;
  static getInstance(type: string): MazeFactory {
    if(! AdvancedMazeFactory.instance) {if (type= = ='bombed') {
        AdvancedMazeFactory.instance = new BombedMazeFactory();
      } else{ AdvancedMazeFactory.instance = new MazeFactory(); }}returnAdvancedMazeFactory.instance; }}Copy the code

Applicable scenario

  • When a class can have only one instance and consumers can access it from a well-known access point;
  • When the unique instance should be extensible by subclassing, as in the example above, by parameters, and the consumer should be able to use an extended instance without changing the code;

advantages

  • You can have effective controlled access to unique instances.
  • Prevents global variables that store unique instances from contaminating the namespace.
  • You can change the specific instance used in the instantiation method.
  • A variable number of instances is allowed. This pattern makes it easy to change your mind and allows singleton classes to have multiple instances at the same time. Because the entry point for instantiation is in one place, it is easy to control the number of instances allowed to exist simultaneously.

Related patterns

  • Many patterns can be implemented using singletons, such as abstract factories, builders, and prototypes.

Reference documentation

  • Design Patterns: Elements of Reusable Object-oriented Software
  • Schematic Design Pattern – Abstract Factory pattern
  • Graphic design pattern — Builder pattern
  • Schematic design pattern — Factory pattern
  • Diagram design pattern – singleton pattern

This article only introduces the creation model, those who are interested in the following model can follow the column or send your resume to ‘chaofeng. LCF ####alibaba-inc.com’.replace(‘####’, ‘@’)

The original address: https://juejin.cn/post/6844903508018364423