In program development, function implementation is flexible and varied, but code quality or maintenance is not easy to quantify, so the design pattern is a powerful tool to let our program can write more reasonable code. This section mainly introduces four design patterns of singleton, factory, abstract factory and builder. By using these four design patterns, we can clearly consider the creation of instances and flexibly divide the creation and implementation.

The singleton pattern

Overview of the singleton pattern

The singleton pattern, also known as the singleton pattern, guarantees that a class has only one instance and provides a method to access that instance. In other words, we create an instance of the same class the second time, which should be exactly the same as the first time.

Perhaps the above terminology is too blunt, but let’s consider some examples from our own lives.

  • We all used some software written record or articles, when we write a lot for some reason after withdrew, the second time to open the software writing before us before we all want to be editing articles, instead, every time our article will open the software to create, it is not very depressed. So we can edit the previous article every time using the archive (draft) function, this article can be compared to a singleton example
  • We use it a lot in programming, and many times we want to use the same object or instance, such as database connection, configuration file cache, browserwindow/documentAnd so on, the creation of instances can lead to a lot of wasted resources or inconsistent access behavior.

From the above scenario, we can see that these examples all have the following characteristics:

  1. Every time a visitor visits, the same instance is returned
  2. If the instance is not created initially, it will be created when we access its global access methods

So it’s not hard to write code like this (es6 for the rest of the tutorial).

class Singleton {
  static _modal = null;
  constructor() {
    if (Modal._modal) {
      // The current instance has already been created
      return Modal._modal;
    }
    // No instance has been created
    return (Modal._modal = this);
  }

  // Globally accessible instance methods
  static getInstance() {
    if (Modal._modal) {
      // The current instance has already been created
      return Modal._modal;
    }
    // No instance has been created
    return (Modal._modal = newModal()); }}const modal1 = new Modal();
const modal2 = new Modal();
console.log(modal1 === modal2); // true

const modal3 = new Modal();
const modal4 = Modal.getInstance(); // Global access methods
console.log(modal3 === modal4); // true
Copy the code

To clarify a bit, this constructor maintains (or mounts itself directly) an instance, and the first time it executes new it checks if the instance has been created and returns if it has, otherwise it goes through the creation process. From the above we can conclude a general implementation of the singleton pattern

  • Singleton: specific class, this is the class we need to access, visitors to get an instance of it;
  • _instance: singleton, which is an instance of a particular class. A particular class usually provides a getInstance method to get the singleton.
  • getInstance: a method to get singletons, or directly from the new operator;

A generic implementation of singletons

But there is a big problem with this approach. We can see that instance _instance is exposed globally as a static property, which we can change externally at any time

const modal3 = new Singleton();
console.log(modal3); // Singleton {}
modal3._instance = { text: "I'm test data." };
console.log(modal3); // Singleton {_instance: {text: 'I am testing data'}}
Copy the code

Isn’t it scary to see that instances can be modified externally at any time?

So how to solve this problem? To solve this problem, we can use the following ways to solve

  1. Creating a singleton pattern using IIFE (Understanding)

    const Singleton = (function () {
      let _instance = null; // Store the singleton
    
      const Singleton = function () {
        if (_instance) return _instance; // Check whether there is a singleton
        _instance = this;
        return _instance;
      };
      Singleton.getInstance = function () {
        if (_instance) return _instance;
        _instance = new Singleton();
        return _instance;
      };
    
      returnSingleton; }) ();const visitor1 = new Singleton();
    const visitor2 = new Singleton(); // New can be used to get singletons
    const visitor3 = Singleton.getInstance(); // You can also get a singleton by getInstance
    
    console.log(visitor1 === visitor2); // true
    console.log(visitor1 === visitor3); // true
    Copy the code

    This way, the _instance variable is used to hold instances, so external code cannot be directly modified. This approach, however, incurs the extra overhead of closures, makes them less readable and increases complexity. Again, the inner class is what we want; this approach simply returns a new class

  2. Creating a singleton in block-level scoped mode

    IIFE way nature or function scope ways to hide within the scope of variables, the ES6 let/const, can by {} block level scope to hide the internal variables:

    let getInstance
    
    {
        let _instance = null        // Store the singleton
    
        const Singleton = function() {
            if (_instance) return _instance     // Check whether there is a singleton
            _instance = this
            this.init()                         // Initialize the operation
            return _instance
        }
    
        Singleton.prototype.init = function() {
            this.foo = 'Singleton Pattern'
        }
    
        getInstance = function() {
            if (_instance) return _instance
            _instance = new Singleton()
            return _instance
        }
    }
    
    const visitor1 = getInstance()
    const visitor2 = getInstance()
    
    console.log(visitor1 === visitor2)// true
    Copy the code
  3. Single Enabling method (recommended)

    / * the Person class * /
    class Person {
        constructor(name, age) {
            this.name = name
            this.age = age
        }
    }
    
    /* Singleton mode enablement method */
    function Singleton(FuncClass) {
        let _instance
        return new Proxy(FuncClass, {
            construct(target, args) {
                return _instance || (_instance = Reflect.construct(FuncClass, args)) // Use new FuncClass(... The args) can also be}})}const PersonInstance = Singleton(Person)
    
    const person1 = new PersonInstance('coder'.25)
    const person2 = new PersonInstance('lyh'.23)
    
    console.log(person1 === person2)	// true
    Copy the code

    The single responsibility principle is adopted to logically decouple the business class from the singleton pattern, which facilitates expansion and subsequent maintenance.

Lazy singleton versus hungry singleton

  • Lazy singleton: Also known as lazy singleton, a lazy singleton of a class that is created lazily, usually innewIt is created when the constructor is executed. The main scenario is: lazy creation can be used when the process of instantiation is expensive and consuming performance, but the place of use does not need to be very timely. All of our previous examples were lazy singletons.
  • Hunk singleton: Create a singleton as soon as the program begins execution. This kind of application is less, for example, if we have multiple tasks to execute, we will maintain the state of a use instance, then need fast synchronization, at this time hanhantype singleton is a good choice. So what exactly is the difference between the two? We can look at the contrast in terms of writing
class People {
  constructor(name) {
    this.name = "coder-lyh"; }}const HungrySingleton = (function (People) {
  // Here's the difference
  // The instance is created during initialization
  let _instance = new People();
  console.log("HungrySingleton initialization", _instance);
  return function () {
    return _instance;
  };
})(People);

const LazySingleton = (function (People) {
  // Here's the difference
  let _instance;
  console.log("LazySingleton initialization", _instance);
  return function () {
    // The new procedure creates the instance
    return _instance || (_instance = new People());
  };
})(People);

const HungryPeople = HungrySingleton(People);// HungrySingleton initialization People {name: 'coder-lyh'}
const LazyPeople = LazySingleton(People);LazySingleton initializes undefined
Copy the code

Practical application of singleton pattern

In the project, I believe you have used full screen loading mask, we know that using this effect can only create one loading instance. Next, the author takes ElementUI as an example. First, Element’s full-screen loading can be called in the following forms

// 1
Vue.use(Loading.directive)
// 2. Service form
Vue.prototype.$loading = service
Copy the code
  • Above is the instruction form registration, the way to use<div :v-loading.fullscreen="true">... </div>;
  • The following is how service form registration is usedthis.$loading({ fullscreen: true });

Using full-screen Loading in service mode is singleton. That is, invoking full-screen Loading again before the previous one is disabled does not create a new Loading instance, but returns an instance of the existing full-screen Loading.

We’ll omit some code for ease of reading

import Vue from 'vue'
import loadingVue from './loading.vue'

const LoadingConstructor = Vue.extend(loadingVue)

let fullscreenLoading

const Loading = (options = {}) = > {
  	// The loading instance was created before
    if (options.fullscreen && fullscreenLoading) {
        return fullscreenLoading
    }

    let instance = new LoadingConstructor({
        el: document.createElement('div'),
        data: options
    })

    if (options.fullscreen) {
      	// Save the instance
        fullscreenLoading = instance
    }
    return instance
}

export default Loading

Copy the code

To explain the above code, the singleton here is our fullscreenLoading, stored globally (closure). It can also be analogous to our cache singleton. That’s the classic use of singletons

Advantages and disadvantages of singletons

The singleton pattern mainly solves the problem of memory resources and maintains the consistency of access. Therefore, it is not difficult to analyze that it has the following advantages:

  • Memory overhead is low because there is only one instance in memory and no need to constantly create or destroy.
  • Can solve the multiple occupation of resources, such as file operations, can avoid simultaneous access to files
  • You can reduce the stress of garbage collection and use less CPU resources

Although singletons can greatly help us save resources, they also have disadvantages, mainly in code extensibility

  • The singleton pattern is not extension-friendly because instances are instantiated by themselves
  • In conflict with the single principle, classes should only care about internal logic, not the creation of instances

So what are the usage scenarios for the singleton pattern?

  • When a class’s instantiation process consumes too many resources, the singleton pattern can be used to avoid performance waste.
  • When a common state is required in a project, the singleton pattern is used to ensure consistent access.

The factory pattern

An overview of the factory pattern

Factory mode: Returns instances of different classes based on different inputs, typically used to create objects of the same type. The main implementation is to separate the implementation of the class from the creation of the object.

For example, to understand, we go to a restaurant to order, we tell the boss about a hundun and a noodle with mixed sauce, then we sit at the table and wait for the boss to cook it and bring it to us, we don’t care how he makes it and cooks it, we just wait to eat it.

The above process is very similar to our factory model, which has the following characteristics:

  • The visitor (me) only needs to access the corresponding property (dish name) to get the corresponding instance (dish) from the factory class (boss)
  • Visitors need not care about the instance creation process

A generic implementation of the factory pattern

According to the above example, we can refine the Factory model. The restaurant can be regarded as Factory and the dish as Product. If we want to obtain the dish instance, we can get the Product instance through the Factory class without paying attention to the process of creating the Product instance. There are mainly the following concepts:

  • Factory: a Factory that returns a product instance;
  • Product: the visitor gets an example of the Product from the factory;

class Factory {
  static getInstance(type) {
    switch (type) {
      case "Product1":
        return new Product1();
      case "Product2":
        return new Product2();
      default:
        throw new Error("The type you entered does not exist"); }}}class Product1 {
  constructor() {
    this.name = "Product1";
  }
  getName() {
    return this.name; }}class Product2 {
  constructor() {
    this.name = "Product2";
  }
  getName() {
    return this.name; }}const prod1 = Factory.getInstance("Product1");
console.log(prod1.getName()); //Product1
const prod2 = Factory.getInstance("Product3"); //Error: The type you entered does not exist
Copy the code

Of course, the above is the implementation of the class, we can see that each product class is instantiated in the factory class, visitors do not need to care about the creation and implementation of the product.

And since we might actually have functional factories, it’s the same idea.

class Dog {
  constructor(name) {
    this.name = name;
  }
  run() {
    console.log(`The ${this.name}Running '); }}class Cat {
  constructor(name) {
    this.name = name;
  }
  run() {
    console.log(`The ${this.name}Running '); }}function createAnimal(type, name) {
  switch (type) {
    case "dog":
      return new Dog(name);
    case "cat":
      return new Cat(name);
    default:
      throw new Error("Incoming type is illegal"); }}const wangcai = createAnimal("dog"."Prosperous wealth.");
wangcai.run(); // Wealth is running
const miaomiao = createAnimal("cat".Cat people);
miaomiao.run();// The cat is running
Copy the code

Factory pattern in source code

  1. We all know that popular frameworks use virtual nodes to reduce real DOM manipulation. So how are these virtual nodes created? Note that the source code provides the createElement method to generate vNodes that map to real DOM nodes.

    // Create an Image node in Vue
    createElement('img', { class: 'avatar'.attrs: { src: '.. /avatar.jpg'}})// React creates the Image node
    React.createElement('img', { src: '.. /avatar.jpg'.className: 'avatar' })
    Copy the code

    It’s easy to imagine that the createElement method should look something like this

    class Vnode (tag.data.children) {... }function createElement(tag, data, children) {
      	return new Vnode(tag, data, children)
    }
    Copy the code

    So, calling createElement is fairly straightforward for the user, but the implementation and creation process is complex and is implemented by the framework, so we don’t have to worry about it.

  2. Vue-router factory mode

    // src/index.js
    export default class VueRouter {
        constructor(options) {
            this.mode = mode	// Routing mode
            
            switch (mode) {           // Simple factory
                case 'history':       / / the history
                    this.history = new HTML5History(this, options.base)
                    break
                case 'hash':          / / the hash
                    this.history = new HashHistory(this, options.base, this.fallback)
                    break
                case 'abstract':      / / the abstract way
                    this.history = new AbstractHistory(this, options.base)
                    break
                default:
                    / /... Initialization failed}}}Copy the code

    The specific routing mode, I believe everyone is familiar with hashRouter and historyRouter, I will not explain more here.

Advantages and disadvantages of the factory model

The factory pattern enables the separation of object creation and implementation and has the following advantages:

  • Encapsulation: the code structure is clear, the realization of the class is decoupled, in line with the least knowledge principle
  • High extensibility: The visitor and object creation process is isolated and complies with open and closed principles

At the same time, it also brings the inevitable disadvantages: it introduces additional system complexity and is prone to unnecessary abstractions.

So when do you use this pattern?

  • Object creation is complex, and visitors do not need to know the process of creation;
  • Processing a large number of small objects with the same properties;

When not to Use the factory pattern: Abuse just adds unnecessary complexity to the system, too much of a good thing.

Abstract Factory pattern

An overview of the

Abstract factory mode has two more words “abstract” than factory mode. Yes, in fact, the most important ability of abstract engineering is to abstract the classes of each link into class clusters.

How to make sense of this? As for the previous example of pastry ordering, let’s first look at the category of dishes we ordered. Whether all dishes have dish names, every dish, whether it is dish, meat or soup, we all know that it is edible. Therefore, all dishes can be abstracted into a kind, which is temporarily called “dish family”. (of course, I may be a rookie in this group, 😄). OK, we understand. So, we are wondering if other restaurants can make these dishes besides the restaurant we ordered. Right? That’s why we’ve abstracted him into a “shopaholic.”

From this we can see that the abstract factory actually abstracts the factory of the class so that its business is used to create the “family of dishes” rather than being responsible for creating an instance of a dish. The key is that the structure of the instance is formulated using abstract classes, and the caller programs directly toward the structure of the instance, decoupled from the concrete implementation of the instance.

Words say a person is, our factory is no longer responsible for creating a product before, but is responsible for defining the specific structure of each product, by the caller to implementation, ah, yes, this is similar to the operation of the apple, ah, I don’t make phones, I only give drawings, vendor to get yourself, finished give me just a matter of money.

A generic implementation of an abstract factory

Also, let’s distill the characteristics of abstract engineering patterns

In the above example, both restaurants and dishes are abstract categories. Dishes that realize abstract categories are concrete products. Products that realize different abstract categories are obtained from factories, and these products can be divided into class clusters according to the realized abstract categories. There are mainly the following concepts:

  • Factory: a Factory that returns a product instance;
  • AbstractFactory: virtual factory, specifying the structure of factory instances;
  • Product: a Product instance that a visitor takes from a factory and implements an abstract class;
  • AbstractProduct: A product abstract class implemented by a concrete product that defines the structure of a product instance;

The schematic diagram is as follows:

Right? A picture is worth a thousand words, so I hope readers can draw their own pictures on a regular basis, which is a particularly useful skill for learning design patterns.

Because JS does not have abstract class, but it does not affect us to learn it, code specific how to implement it, let’s have a look:

class AbstractProduct {
  constructor() {
    if (new.target === AbstractProduct) {
      throw new Error("Abstract classes cannot be instantiated"); }}operate() {
    throw new Error("Abstract methods cannot be called"); }}class Product1 extends AbstractProduct {
  constructor(kind = "product1") {
    super(a);this.kind = kind;
  }
  operate() {
    console.log(`The ${this.kind} can operate machine`); }}class Product2 extends AbstractProduct {
  constructor(kind = "product2") {
    super(a);this.kind = kind;
  }
  operate() {
    console.log(`The ${this.kind} can operate machine`); }}class AbstractFactory {
  constructor() {
    if (new.target === AbstractFactory) {
      throw new Error("Abstract classes cannot be instantiated"); }}createProduct() {
    throw new Error("Abstract methods cannot be called"); }}class Factory extends AbstractFactory {
  constructor(type) {
    super(a);this.type = type;
  }
  createProduct() {
    const type = this.type;
    switch (type) {
      case "product1":
        return new Product1();
      case "product2":
        return new Product2();
      default:
        return null; }}}const factory1 = new Factory("product1");
factory1.createProduct().operate(); //product1 can operate machine
const factory2 = new Factory("product2");
factory2.createProduct().operate();// product2 can operate machine
Copy the code

Not every factory needs to inherit the abstract factory class. If there is only one factory, you can use the factory mode directly. This requires the flexibility of the developer.

Advantages and disadvantages of the abstract factory pattern

Advantages of abstract schema:

Abstract product class will abstract out the structure of the product, visitors do not need to know the concrete implementation of the product, only need to product-oriented structure programming, decoupled from the concrete implementation of the product;

Disadvantages of abstract patterns:

  • Extending the product class of the new class cluster is difficult because you need to create a new abstract product class and modify the factory class to violate the open close principle.
  • Added system complexity, new classes, and new inheritance relationships;

Usage scenarios

If a group of instances all have the same structure, then the abstract factory pattern can be used. Differences between the factory pattern and the abstract Factory pattern:

  • The factory pattern focuses on the creation of individual product instances;
  • Abstract factory mode focuses on the creation of product class cluster instances. If the product class cluster has only one product, then the abstract factory mode degenerates into factory mode. Use it flexibly based on scenarios.

Builder model

An overview of the

The Builder pattern, also known as the generator pattern, builds a complex object in steps and allows step-by-step construction. The same build process can use different representations to separate the build layer from the presentation layer of a complex object.

As mentioned earlier, the factory pattern is not concerned with the creation of objects, but only with the results. In contrast, the Builder pattern is concerned with the creation of objects. Let’s use a real-life example to illustrate the Builder pattern:

Let’s say we need to build a car. A car is a product made up of several parts: a body, an engine, and tires. Instead of making each part themselves, car manufacturers hand over the manufacturing of the parts to the corresponding car parts manufacturer, and assemble the whole car themselves. Each part of the vehicle is a relatively independent individual, has its own production process, multiple parts through a series of assembly together to form a complete car.

In the scenario, there are the following characteristics:

  • The vehicle manufacturer (commander) does not need to know the production process of parts, the production process of parts is generally completed by the parts manufacturer (builder);
  • The vehicle manufacturer (commander) decides how to assemble the parts to get the final product;

Common implementation of build patterns

We refine the Builder model, where the manufacturer is equivalent to the Director, who is responsible for assembling different parts into the final Product, while the component producer is the component manufacturer equivalent to the Builder, through which we can obtain the desired complex Product object. Then through interviews with different commanders to obtain the assembly of different products. There are mainly the following concepts:

  • Director: the Director who calls the specific realization of the components in the builder to assemble the components, which is equivalent to the vehicle assembly plant, and finally returns the assembled products;
  • Builder: the production mode of different parts is called by the commander. It is the real producer of parts, but there is no assembly process of parts.
  • Product: a complex object to be returned to the visitor

The concept map is as follows:

How to implement the specific code? Let’s see:

// Builder, component production
class ProductBuilder {
    constructor(param) {
        this.param = param
    }
    
    /* production parts, part1 */
    buildPart1() {
        / /... Part1 Production process
        this.part1 = 'part1'
        
    }
    
    /* Production parts, Part2 */
    buildPart2() {
        / /... Part2 Production process
        this.part2 = 'part2'}}/* Conductor, responsible for assembling the final product */
class Director {
    constructor(param) {
        const _product = new ProductBuilder(param)
        _product.buildPart1()
        _product.buildPart2()
        return _product
    }
}

// Get the product instance
const product = new Director('param')
Copy the code

In general, the Builder mode works with the chain mode to make it readable. We will continue with the chain mode later. Here you can see how the builder mode works with the chain mode:

// Builder, auto parts manufacturer
class CarBuilder {
    constructor(param) {
        this.param = param
    }
    
    /* production parts, part1 */
    buildPart1() {
        this.part1 = 'part1'
        return this
    }
    
    /* Production parts, Part2 */
    buildPart2() {
        this.part2 = 'part2'
        return this}}// Get the product example
const benchi1 = new CarBuilder('param')
    .buildPart1()
    .buildPart2()

Copy the code

In actual combat

  • When we write functions, we may encounter many parameter constructors, and when we want to set values, we can not clearly know the location of each attribute. Such as

    // Car builder
    class CarBuilder {
        constructor(engine, weight, height, color, tyre, name, type) {
            this.engine = engine
            this.weight = weight
            this.height = height
            this.color = color
            this.tyre = tyre
            this.name = name
            this.type = type
        }
    }
    
    const benchi = new CarBuilder('High horsepower engine'.'2ton'.'white'.'Big tire'.'Mercedes'.'AMG')
    Copy the code
  1. Refactoring implementation version I:

    class CarBuilder {
      constructor(engine, weight, height, color, tyre, name, type) {
        this.engine = engine;
        this.weight = weight;
        this.height = height;
        this.color = color;
        this.tyre = tyre;
        this.name = name;
        this.type = type;
      }
      setCarProperty(key, value) {
        if (Object.getOwnPropertyNames(this).includes(key)) {
          this[key] = value;
          return this;
        }
        throw new Error(`Key Error: ${key}Is not a property 'on the instance); }}const benchi = new CarBuilder()
    .setCarProperty("engine"."Engine")
    .setCarProperty("weight"."2ton")
    .setCarProperty("height"."2000mm")
    .setCarProperty("color"."white")
    .setCarProperty("tyre"."Big tire")
    .setCarProperty("name"."Mercedes")
    .setCarProperty("type"."AMG");
    console.log(benchi);
    /** CarBuilder {engine: 'engine ', weight: '2ton', height: '2000mm', color: 'white', name:' Benz ', type: 'AMG' } */
    Copy the code

By setting this method, we can not care about the parameter position of the attribute, just need to set the corresponding value in the corresponding attribute. Is there a more elegant operation? Yes, there is. Let’s look at the implementation of version 2:

  1. Refactoring version 2

    class CarBuilder {
      constructor(engine, weight, height, color, tyre, name, type) {
        this.engine = engine;
        this.weight = weight;
        this.height = height;
        this.color = color;
        this.tyre = tyre;
        this.name = name;
        this.type = type;
      }
      setPropertyByChain() {
        Object.getOwnPropertyNames(this).forEach((key) = > {
          // Constructor name
          const funcName = `set${key.replace(/^\w/g, (str) => str.toUpperCase())}`;
          // Save to the instance
          this[funcName] = (value) = > {
            this[key] = value;
            return this;
          };
        });
        return this; }}const baoma = new CarBuilder()
      .setPropertyByChain()
      .setEngine("High-powered engine")
      .setWeight("2ton")
      .setHeight("2000mm")
      .setColor("white")
      .setTyre("Big tire")
      .setName("Mercedes")
      .setType("AMG");
    console.log(baoma);
    /** CarBuilder {engine: 'engine ', weight: '2ton', height: '2000mm', color: 'white', name:' Benz ', type: 'AMG', setEngine: [Function (anonymous)], setWeight: [Function (anonymous)], setHeight: [Function (anonymous)], setColor: [Function (anonymous)], setTyre: [Function (anonymous)], setName: [Function (anonymous)], setType: [Function (anonymous)] } */
    
    Copy the code

    We simply turn each property into the corresponding setting function, and set the corresponding value according to the property function. But the big problem with this is that we have a bunch of setters for no reason, so we have to make a reasonable choice and choose the one that works best.

Advantages and disadvantages of the build model

Advantages of the Builder model:

  • Using the Builder pattern, the process of building a product is separated from the performance of the product, that is, the creation algorithm of the product is separated from the implementation of the product composition, so that visitors do not have to know the details of the implementation of the product parts;
  • The extension is convenient, if you want to produce a new product with different assembly sequence or way, then you can directly create a new commander, without modifying the existing code, in line with the open and closed principle;
  • Better reuse. The builder mode separates the product creation algorithm from the realization of product composition, so the algorithm of product creation can be reused, and the realization of product components can also be reused, bringing great flexibility.

Disadvantages of the Builder model:

  • The Builder mode is generally suitable for similar components between products. If there is a large difference between products and the reusability is not high, the builder mode should not be used.
  • The creation of instances adds a lot of extra structure, which undoubtedly adds a lot of complexity. If the object granularity is small, then we are better off creating the object directly;

Scenarios for the Builder pattern

  • The same method, different execution sequence, produce different products, can adopt the builder mode;
  • The components of products are similar. When different products are obtained by assembling different components, the builder model can be adopted.

conclusion

In this chapter, we mainly learn four kinds of creation behavior patterns, which are singleton pattern, factory pattern, abstract factory pattern and Builder pattern. We can understand the difference and connection between them

  1. Singleton mode is a way to ensure that only one instance of a class is created in memory, which can save memory consumption and ensure access uniformity. But at the same time, there are shortcomings of poor expansion and chaotic structure. Among them, the singleton pattern is divided into lazy singleton and hungry singleton, lazy singleton is used when the instance is created, hungry singleton is the program initialization immediately created instances. When we need to consider performance waste and common state issues, we can consider singleton patterns.
  2. The factory pattern and the abstract factory pattern are very similar design patterns. It can be understood that the factory pattern is a weakened pattern of the abstract factory pattern. What both design patterns have in common is that they do not care about the instance creation and implementation process, and use a factory approach to decouple the implementation and creation process to improve scalability. The downside is increased complexity and abstraction. The difference between abstract factory mode and factory mode lies in the clustering and concretization of factory class. The factory class of abstract factory is a family, which can realize a variety of factories, while the factory class of factory mode has only one. Abstract factory mode focuses on the creation of product class cluster instances. If the product class cluster has only one product, then the abstract factory mode degenerates into factory mode. Use it flexibly based on scenarios.
  3. Both the Builder pattern and the factory pattern end up creating a complete product, but in the Builder pattern we care more about the object creation process and modularize the way objects are created to better reuse those modules. Of course, the Builder mode and the factory mode can also be used together. For example, the builder usually provides different part implementations, so the factory mode can be used to provide specific part objects, which can be assembled by the director.