Design Pattern ii — Creation pattern

The creation pattern is a design pattern that deals with the object creation process, using the appropriate pattern to create objects based on the actual situation.

The creation mode mainly encapsulates the concrete classes needed by the system, realizes the creation and combination of these concrete classes internally, and hides the details of this process externally. The object creation and composition process is not directly accessible to outsiders. The user only needs to care about when, where, by whom, and how the object is created.

The creation mode mainly includes the following types:

  • Simple Factory modelSimple Factory
  • Factory method patternFactory Method
  • Abstract Factory patternAbstract Factory
  • Builder modelBuilder
  • The prototype patternPrototype
  • The singleton patternSingleton

Usage scenarios

A program that implements a maze game is used in Design Patterns: Elements of Reusable Object-oriented Software to illustrate the relationship of centralized Patterns related to creative Patterns.

“The maze and the game will vary slightly from mode to mode. Sometimes the game will be as tight as finding the exit of a maze; In this case, the player may only get to part of the maze. Sometimes mazes contain problems to be solved and dangers to be overcome, and these games may provide maps of parts of the maze that have already been explored. We’re going to ignore a lot of the details in mazes and whether there’s one or more players in a maze game, we’re just going to focus on how mazes are created.”

— Design patterns: The Foundation of Reusable Object-oriented Software, P 54

In creating the maze game, we can simplify the maze into a square room with many parts.

For each room we can define a base class MapSite that contains the property direction for each of the room’s four walls (or directions) : [“North”, “South”, “East”, “West”], and each has a method Enter(), which enters the room.

On top of this base class, we need to define a concrete class Zoom that contains its own room number, zoomNumber, and reference sides in relation to the surrounding rooms.

Since not all rooms have doors on every side, we need two more classes: Wall Wall and Door Door.

Now that the parts are defined, we need a Maze class representing a collection of rooms to find specific rooms based on their zoomNumber, and finally a MazeGame class to create a complete Maze game. The internal implementation of an instantiation of the maze game method createMaze().

The example above looks simple, but it defines so many classes that it can be very complicated when it comes to creating an instance of a maze game. It is possible to initialize each side of the room as a wall or door when you define Zoom, but if you need to add a new part later (such as a DoorNeedSpell, or a room with a “treasure” EnchantedRoom), you need to change the code in many places. Does not conform to the principle of design pattern reuse.

From this example, we can summarize the scope of the creation pattern:

  1. Internal objects, products, interfaces, etc. are independent of each other, like walls and doors;
  2. Hide the concrete implementation of the library, providing only interfaces and methods, and accessible properties;
  3. A class wants its subclasses to implement the objects it creates;
  4. A set of related objects is designed to be used together.

There is also a “singleton pattern” that is used when a class has only one instance.

1. Simple Factory mode (not one of the 23 GoF modes)

Before we look at the five specific patterns of the creative pattern, we can look at the “Simple Factory pattern.”

Simple Factory Pattern (ALSO known as Static Factory Method) is not one of the 23 GoF Patterns in principle. The Four authors of The book Elements of Reusable Object-oriented Software are known as the Gang of Four design patterns.

1.1 the characteristics of

The simple factory pattern typically provides only an interface to factory methods that create instance objects, and moves the concrete object instance process to a concrete subclass factory. You only need to pass in the corresponding type parameters to create instance objects of the corresponding type.

1.2 the advantages and disadvantages

Advantages:

  1. Is there specific judgment logic in a factory class that determines which class of instance objects to create when and under what circumstances
  2. New concrete subclasses can be replaced and added using external configuration files without modifying the client code
  3. The client does not need to know the internal implementation, but only needs to know the type to get the corresponding instance object

Personally, in JavaScript, the first advantage is one of the more attractive ones. But the simple factory model has more disadvantages.

Disadvantages:

  1. The factory class is overloaded with responsibilities and can be bloated when there are many types
  2. Every time a new subclass is added, a new judgment logic is added, which is not conducive to reading and maintenance, and violates the open and closed principle
  3. Extend the difficult
  4. Unable to inherit

1.3 structure

A simple factory consists of three main parts:

  1. Outer simple factory class
  2. The base abstract class for all instances, which is the parent class of all type instances
  3. A concrete factory subclass that is responsible for creating instances of the specified type

1.4 implementation

The simple factory pattern is primarily about writing a common base abstract class for instance objects, in which different concrete classes are called to create and return instance objects depending on the parameters passed in.

abstract class Creator {
    public someOperation(type: string) :string {
        if (type= = ="prod") {
            return new ConcreteProduct1();
        } else {
            return newConcreteProduct2(); }}}interface Product {
    operation(): string;
}

class ConcreteProduct1 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct1}'; }}class ConcreteProduct2 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct2}'; }}Copy the code

1.5 Application Scenarios

This can be used when the factory class is responsible for a small number of object types, or when the schema is fixed and rarely changes.

2. Factory method pattern

In the “Simple Factory pattern” in the previous section, we learned that the simple factory pattern violates the “open and closed principle,” while the factory method pattern is a further abstraction of the simple factory pattern to satisfy the “open and closed principle.”

2.1 the characteristics of

Compared with the simple Factory pattern, the Factory method pattern splits the factory class and creates a base factory class, which makes the code structure clearer. When introducing a new type, there is no need to modify the original code, but to create a new concrete factory class on top of the base factory class.

2.2 structure

The factory method pattern consists of four parts:

  1. Base (abstract) factory class
  2. Specific Factory
  3. Base (abstract) instance classes
  4. Concrete instance class, corresponding to the concrete factory class

2.3 the advantages and disadvantages

According to the structure of this pattern, the relationship between several parts can be clearly seen. By splitting the two base classes to create corresponding concrete implementation classes, the code structure is clearer, but instead, the amount of code and repeated code is increased.

Advantages:

  • Users only need to know the name of the specific factory to get the product they want, without knowing the specific creation process of the product.
  • Increased flexibility, for the creation of new products, only need to write a corresponding factory class.
  • A typical decoupling framework. A high-level module only needs to know the abstract class of the product and does not need to care about other implementation classes, satisfying Demeter’s law, dependency inversion principle and Richter’s substitution principle.

Disadvantages:

  • It is easy to have too many classes and add complexity
  • The system is abstract and difficult to understand
  • Abstract products can only produce one product, which can be solved by using the abstract factory model.

2.4 implementation

Using the example “1.4 Simple Factory Pattern – Implementation”, we split the factory class to get a simple implementation of the factory method pattern.

abstract class Creator {
    public abstract factoryMethod(): Product;
    public someOperation(): string {
        const product = this.factoryMethod();
        returnproduct.operation(); }}class ConcreteCreator1 extends Creator {
    public factoryMethod(): Product {
        return newConcreteProduct1(); }}class ConcreteCreator2 extends Creator {
    public factoryMethod(): Product {
        return newConcreteProduct2(); }}interface Product {
    operation(): string;
}

class ConcreteProduct1 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct1}'; }}class ConcreteProduct2 implements Product {
    public operation(): string {
        return '{Result of the ConcreteProduct2}'; }}Copy the code

Abstract factory pattern

In 1.2 Factory Method pattern, we know that factory method pattern is to split the factory class of “simple factory pattern”, and the “concrete factory class” after splitting corresponds to “concrete instance class” one by one. But all concrete factory classes and concrete instance classes share the same abstract factory class and the same abstract instance class, respectively, so that all instances and creation methods are on the same level.

The abstract factory pattern is an interface that creates a set of related or interdependent objects.

Definition: A schema structure that provides access classes with an interface to create a set of related or interdependent objects, and that access classes can obtain different levels of products of the same family without specifying the concrete class of the product they want.

3.1 the characteristics of

The abstract factory pattern can be seen as an upgraded version of the factory method pattern, which can produce only one level of products, while the abstract factory pattern can produce multiple levels of products.

Each concrete factory class can create different types of instances of the same family (brand).

For example, “Huawei” and “Apple” are both based on a basic category of “technology companies”. “Apple” can design and produce “mobile phones”, “computers” and “tablets”, and “Huawei” can also produce “mobile phones”, “computers” and “tablets”. But “Huawei phone” and “iPhone” are both in the basic category of “phone”, “Apple Computer” and “Huawei Computer” are in the basic category of “computer” and so on. The parts that make up the abstract factory pattern intersect and influence each other.

3.2 the advantages and disadvantages

Advantages:

Abstract factory mode from the name and characteristics, we can know that it is to optimize the factory method mode was born, so it also has all the advantages of factory method mode; And it introduces the concept of product family, the system can only use the instance object of the same family at the same time; The abstract factory pattern also isolates the implementation of each concrete class, making it easier to replace the existing factory and not modify existing concrete classes when introducing new concrete classes.

Disadvantages:

Because the connection between each part is quite deep, when a specific class is added, it is necessary to increase the connection between the new class and the outside world, and the scope of change is relatively large.

3.3 structure

Because the number of corresponding factory classes and instance classes in this mode is uncertain, it can only be divided into four components according to the functions and responsibilities of each part:

  1. Abstract (Base) Factory class: The parent of all concrete factory classes in this product family, defining methods for creating various instance objects
  2. Concrete factory classes: Implement methods defined by abstract classes and create corresponding instance objects
  3. Abstract (base) instance class: Defines the properties, methods, and so on of all instance objects of that type
  4. Concrete instance class: Implements all properties, methods, and so on defined by the abstract instance class

Parse with two concrete factory classes and two abstract instance classes:

3.4 implementation

interface AbstractFactory {
    newProduct1(): Product1;
    newProduct2(): Product2;
}

class ConcreteFactory1 implements AbstractFactory {
    public newProduct1(): Product1 {
        return new ConcreteProduct11();
    }

    public newProduct2(): Product2 {
        return newConcreteProduct21(); }}class ConcreteFactory2 implements AbstractFactory {
    public newProduct1(): Product1 {
        return new ConcreteProduct12();
    }

    public newProduct2(): Product2 {
        return newConcreteProduct22(); }}interface Product1 {
    usefulFunctionA(): string;
}

class ConcreteProduct11 implements Product1 {
    public usefulFunctionA(): string {
        return 'The result of the product A1.'; }}class ConcreteProduct12 implements Product1 {
    public usefulFunctionA(): string {
        return 'The result of the product A2.'; }}interface Product2 {
    usefulFunctionB(): string;
    anotherUsefulFunctionB(collaborator: Product1): string;
}

class ConcreteProduct21 implements Product2 {
    public usefulFunctionB(): string {
        return 'The result of the product B1.';
    }
    public anotherUsefulFunctionB(collaborator: Product1): string {
        const result = collaborator.usefulFunctionA();
        return `The result of the B1 collaborating with the (${result}) `; }}class ConcreteProduct22 implements Product2 {
    public usefulFunctionB(): string {
        return 'The result of the product B2.';
    }
    public anotherUsefulFunctionB(collaborator: Product1): string {
        const result = collaborator.usefulFunctionA();
        return `The result of the B2 collaborating with the (${result}) `; }}Copy the code

4. Builder mode

Definition: To separate the construction of a complex object from its representation, so that the same construction process can create different representations.

4.1 the characteristics of

By definition, the Builder pattern can break down the creation process of a complex object into a step-by-step creation process. Each step is a component. The components of the object created by the Builder pattern are unchanged, but each component (each step) can be flexibly selected according to different conditions.

4.2 the advantages and disadvantages

Advantages:

  1. Process decoupling and good encapsulation: the object creation process is decoupled from the object itself, and the client or user only needs to know the corresponding parameters of the object to get the corresponding object
  2. Good expansibility: each part of the internal independent, do not affect each other, in line with the open and closed principle
  3. Each specific component complies with the principle of single responsibility

Disadvantages:

  1. This mode is only suitable for generating objects that have much in common. If the required objects are very different, this mode is not applicable
  2. Internal code redundancy, the internal composition of more objects, may lead to the system becomes very large; And later maintenance and modification is more difficult

While the specific components of the interior may be functionally distinct and independent, when the object attributes that need to be generated are too complex, the outermost builder code is too large and not suitable for the rest of the types of objects.

4.3 structure

As can be seen from the characteristics of the builder mode, there is no fixed number of components in this mode, which can only be divided into components (elements), roughly including four elements:

  1. Abstract Factory Builder: The outermost abstract class that defines the properties and methods of the generated object and usually includes a method that returns the generated object
  2. Concrete Builder (Concrete Factory class, multiple) : A factory class that implements various properties or methods inside an object
  3. Products: A complex object containing the corresponding components, which are created and implemented by the abstract factory class (also considered the final generated object)
  4. Director: Acts as a management role to invoke the corresponding components or steps according to different parameters (required types) to complete the creation of objects, without involving the specific information of the final object

4.4 implementation

interface Builder {
    producePartA(): void;
    producePartB(): void;
    producePartC(): void;
}

class Product1 {
    public parts: string[] = [];
    public listParts(): void {
        console.log(`Product parts: The ${this.parts.join(', ')}\n`); }}class ConcreteBuilder1 implements Builder {
    private product: Product1;
    constructor() {
        this.reset();
    }
    public reset(): void {
        this.product = new Product1();
    }
    public producePartA(): void {
        this.product.parts.push('PartA1');
    }
    public producePartB(): void {
        this.product.parts.push('PartB1');
    }
    public producePartC(): void {
        this.product.parts.push('PartC1');
    }
    public getProduct(): Product1 {
        const result = this.product;
        this.reset();
        returnresult; }}class Director {
    private builder: Builder;
    public setBuilder(builder: Builder): void {
        this.builder = builder;
    }
    public buildMinimalViableProduct(): void {
        this.builder.producePartA();
    }
    public buildFullFeaturedProduct(): void {
        this.builder.producePartA();
        this.builder.producePartB();
        this.builder.producePartC(); }}Copy the code

Specific implementation steps:

  1. Create an abstract factory class/interfaceBuilderDefine common abstract methods
  2. Defining the product class (type definition for the final instance)Product1Properties and methods of
  3. inheritanceBuilderTo create a concrete implementation classConcreteBuilder1And provides a public method to get the final instance objectgetProduct()
  4. Define the guideDirector, provide different methods to create corresponding products (object instances)

Use:

// Client-side methods
// Create a guide instance
const director = new Director();

// Create the execution function
function clientFunc(director: Director) {
    // Create an instance of the concrete implementation class and pass it to the configuration method as a configuration parameter for the guide instance.
    const builder = new ConcreteBuilder1();
    director.setBuilder(builder);
	
    // Create a Mini instance using the configured guide instance
    director.buildMinimalViableProduct();
    builder.getProduct().listParts(); // Product parts: PartA1

    // Create a full instance using the configured guide instance
    director.buildFullFeaturedProduct();
    builder.getProduct().listParts(); // Product parts: PartA1, PartB1, PartC1

    // You can create a new instance directly using the corresponding instance of the concrete implementation class
    builder.producePartA();
    builder.producePartC();
    builder.getProduct().listParts(); // Product parts: PartA1, PartC1
}

// Execute the client method
clientFunc(director);
Copy the code

4.5 Application Scenarios

The product class is very complex, and different execution order can get different results; Or you can use this pattern when you need components in a different order for different products.

In my opinion, this pattern is a simplified way of “object creation” process, through unified management (guide), to avoid the client in the use of multiple passes to participate in the repeated definition. Using this pattern to design code can avoid redundant code generated in the subsequent development process, for objects with many same attributes or methods, this pattern has good applicability, but multiple creation methods are encapsulated in the guide, it is difficult to later maintenance and upgrade.

5. Prototyping

Definition: Using an already created instance as a prototype, to create a new object identical or similar to the prototype by copying the prototype object.

5.1 the characteristics of

You can copy an existing object without relying on the class to which the existing object belongs. And it is very efficient to create objects in this way, without knowing the creation process and details of the original object.

5.2 the advantages and disadvantages

Advantages:

  1. You can copy objects directly without coupling them to the specific classes to which they belong, making it easier to generate complex objects
  2. You can avoid running the original code repeatedly

Disadvantages:

  1. Cloning complex objects of circular reference type can be cumbersome
  2. One needs to be configured for each classclone()Method, and the method is inside the class, each change needs to modify the class code, violating the open close principle

5.3 structure

The archetypal pattern contains the following primary roles.

  1. Abstract stereotype classes: Specify the interfaces that concrete stereotype objects must implement
  2. Concrete stereotype class: Implements abstract stereotype classesclone()Method, an object that can be copied
  3. Access classes: Use the concrete stereotype classesclone()Method to copy the new object

5.4 implementation

class Prototype {
    public primitive: any;
    public component: object;
    public circularReference: ComponentWithBackReference;
	// Define a public clone method that returns the clone result
    public clone(): this {
        const clone = Object.create(this);
        clone.component = Object.create(this.component); clone.circularReference = { ... this.circularReference,prototype: { ...this },
        };
        returnclone; }}class ComponentWithBackReference {
    public prototype;

    constructor(prototype: Prototype) {
        this.prototype = prototype; }}Copy the code

6. Singleton mode

Definition: a pattern in which a class has only one instance and the class can create its own instance.

The purpose of the singleton pattern is to create an instance of a class, ensure that there is only one instance of the class, and provide a global access node. For example, a country has only one official government, a feudal dynasty has only one emperor, a school has only one headmaster and so on.

6.1 the characteristics of

The singleton pattern has the following characteristics:

  1. There is only one instance object
  2. The instance object can only be created by the singleton
  3. Only a global access node for an instance of the singleton class is provided externally

6.2 the advantages and disadvantages

Because the singleton pattern has only one instance, it can reduce memory and resource overhead and optimize access to shared resources. But the singleton pattern also has several serious drawbacks:

  1. Generally, there are no abstract classes and extension is difficult
  2. The singleton class is overloaded, acting both as a factory, providing factory methods for creating objects, and as a product, containing some business logic
  3. Too much code is not conducive to the later debugging

Singletons can also be created with multiple instances in multiple threads (this problem does not exist in JavaScript)

6.3 structure

The singleton pattern contains primarily one role: the singleton class

Screenshot from RefeACmotoring

6.4 implementation

The singleton pattern can be implemented in two ways: “lazy singleton” and “hungry singleton.” The difference between the two is whether a singleton instance is initialized at class load time.

6.4.1 Lazy singleton

A method to get a singleton is usually provided and instantiated on the first call.

TypeScript.

class LazySingleton {
    private static instance: Singleton;
    // Constructors must be private to avoid external instantiation with the new keyword
    private constructor(){}// Create an instance of the external access method if it does not exist
    public static getInstance(): Singleton {
        if(! Singleton.instance) { Singleton.instance =new Singleton();
        }
        return Singleton.instance;
    }
    // Business logic
    public someBusinessLogic() {
        // ...}}Copy the code

JavaScript:

function LazySingleton() {
    this.instance = null;
}
LazySingleton.getInstance = function() {
    if(!this.instance) {
        this.instance = new LazySingleton();
    }
    return this.instance;
}
LazySingleton.someBusinessLogic = function() {}
Copy the code

6.4.2 Hangry singleton

Directly instantiate a singleton class in a class to ensure that a corresponding instance is created when an interface is invoked.

class LazySingleton {
    private static instance: Singleton = new Singleton();
    // Constructors must be private to avoid external instantiation with the new keyword
    private constructor(){}// Create an instance of the external access method if it does not exist
    public static getInstance(): Singleton {
        return Singleton.instance;
    }
    // Business logic
    public someBusinessLogic() {
        // ...}}Copy the code

The JavaScript implementation is no longer implemented here

When implementing the singleton pattern, note: 1. Class constructors must be private, and 2. Need to provide a corresponding instance variable (instance above), 3. There needs to be an external static factory method (or a method to getInstance variables, getInstance())

6.5 Application Scenarios

Because singletons can save resources and have only one self-generated instance, they can be used when the system has and only needs one program or object.

Such as database connection pools, global caches, window objects for browsing, or pop-ups of user information in the system, etc.

6.6 optimization

We know that the biggest benefit of the singleton pattern is that there is only one corresponding instance variable globally, but if there are multiple types, we need to write multiple singleton classes. To simplify the amount of code and code logic, you can present the logic that manages singletons (that is, the part that determines whether a singleton class has an instance). For the part that implements/creates an instance of the singleton class, pass it as a parameter to the management method.

const GetSingle = function(fn) {
    let instance = null;
    return function() {
        if(! instance) { instance = fn.apply(this.arguments);
        }
        return instance;
        / / can be simplified to return the instance | | fn. Apply this, the arguments ()}}const createLoginLayer = function() {
    const div = document.createElement("div");
    div.innerHTML = "I'm logging in to the floating window.";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
}

const CreateSingleLoginLayer = GetSingle(createLoginLayer);
Copy the code

Smile: 🙂 🙂 : Smile: :

References:

  1. Refactoringguru: Design patterns, refactoring

  2. Design Patterns (C Language Chinese)

  3. Graphic design patterns

  4. Zen of Design Pattern qin Xiaobo, China Machine Press