This is the 11th day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021

The Factory Pattern, which returns instances of different classes according to different inputs, is generally used to create objects of the same class. The main idea of the factory approach is to separate object creation from object implementation.

  • Abstract Factory: Factory abstraction of a class enables its business to be used to create clusters of product classes, rather than being responsible for creating instances of a class of products. 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.
  • We know that JavaScript is not a strong object-oriented language, so the design pattern of using traditional compiled languages like JAVA, C#, C++ is not the same as JavaScript, For example, JavaScript does not have native classes and interfaces (though ES6+ is gradually providing similar syntactic sugar), which can be worked around. What matters most is the core idea behind the design pattern and the problem it is trying to solve.

1. The abstract factory pattern you’ve seen before

Again, use the restaurant example used in the factory pattern in the previous section.

  • You come to the restaurant in the community again, and tell the boss to say a piece of shredded pork with fish flavor, to a piece of kung pao chicken, to a piece of tomato and egg soup, to a piece of spare ribs soup (today may be more like soup). No matter what kind of dish or soup, they all have the same properties, such as food can be eaten, soup can be drunk. So we can eat any dish we get, and we can drink any soup we get. The same is true for restaurants. One restaurant can do dishes and soups, and another restaurant can do soups, so the two restaurants have the same functional structure.
  • The following scenarios are examples of the abstract factory pattern. Dishes belong to abstract products, and the attributes of specific products are determined. Like the previous factory model, restaurants are responsible for the specific production of product instances, and visitors get the products they want from the owner. As long as we order soup, even if it hasn’t been cooked yet, we know it’s ok to drink. By extension, restaurant functions can also be abstracted (the abstract restaurant class). Restaurant instances that inherit this class all have the function of cooking and soup, which also completes the structural constraints of the abstract class on instances.
  • In similar scenarios, these examples have characteristics: whenever an instance of an abstract class is implemented, the structure specified by the abstract class is implemented;

2. Example code implementation

We know that JavaScript is not strongly object-oriented and does not provide abstract classes (at least not yet), but it can emulate abstract classes. Target (); throw new Error() in the parent class method. If the parent class does not implement this method, it will throw an Error.

/* ES6 class */
class AbstractClass1 {
    constructor() {
        if (new.target === AbstractClass1) {
            throw new Error('Abstract classes cannot be instantiated directly! ')}}/* Abstract method */
    operate() { throw new Error('Abstract methods cannot be called! ')}}/* Abstract class, ES5 constructor */
var AbstractClass2 = function () {
    if (new.target === AbstractClass2) {
        throw new Error('Abstract classes cannot be instantiated directly! ')}}/* Abstract method, using prototype method to add */
AbstractClass2.prototype.operate = function(){ throw new Error('Abstract methods cannot be called! ')}Copy the code

Let’s use JavaScript to implement the restaurant example described above.

First use the prototype approach:

/* Hotel method */
function Restaurant() {}

Restaurant.orderDish = function(type) {
    switch (type) {
        case 'Shredded pork with fish flavor':
            return new YuXiangRouSi()
        case 'Kung Pao Chicken':
            return new GongBaoJiDing()
        case 'Nori egg soup':
            return new ZiCaiDanTang()
        default:
            throw new Error('We don't have this in our shop. - ')}}/* * * */
function Dish() { this.kind = 'food' }

/* Abstract method */
Dish.prototype.eat = function() { throw new Error('Abstract methods cannot be called! ')}/* * * * * */
function YuXiangRouSi() { this.type = 'Shredded pork with fish flavor' }

YuXiangRouSi.prototype = new Dish()

YuXiangRouSi.prototype.eat = function() {
    console.log(this.kind + The '-' + this.type + 'really sweet ~')}/* Kung pao chicken */
function GongBaoJiDing() { this.type = 'Kung Pao Chicken' }

GongBaoJiDing.prototype = new Dish()

GongBaoJiDing.prototype.eat = function() {
    console.log(this.kind + The '-' + this.type + 'Reminds me of grandma's cooking.')}const dish1 = Restaurant.orderDish('Shredded pork with fish flavor')
dish1.eat()
const dish2 = Restaurant.orderDish('Braised spare Ribs')

// Output: dish - fish flavor meat really delicious ~
// Output: Error This store does not have this -. -useclass/* Hotel method */class Restaurant {
    static orderDish(type) {
        switch (type) {
            case 'Shredded pork with fish flavor':
                return new YuXiangRouSi()
            case 'Kung Pao Chicken':
                return new GongBaoJiDin()
            default:
                throw new Error('We don't have this in our shop. - ')}}}/* * * */
class Dish {
    constructor() {
        if (new.target === Dish) {
            throw new Error('Abstract classes cannot be instantiated directly! ')}this.kind = 'food'
    }
    
    /* Abstract method */
    eat() { throw new Error('Abstract methods cannot be called! ')}}/* * * * * */
class YuXiangRouSi extends Dish {
    constructor() {
        super(a)this.type = 'Shredded pork with fish flavor'
    }
    
    eat() { console.log(this.kind + The '-' + this.type + 'really sweet ~')}}/* Kung pao chicken */
class GongBaoJiDin extends Dish {
    constructor() {
        super(a)this.type = 'Kung Pao Chicken'
    }
    
    eat() { console.log(this.kind + The '-' + this.type + 'Reminds me of grandma's cooking.')}}const dish0 = new Dish()  										// Output: Error abstract method cannot be called!
const dish1 = Restaurant.orderDish('Shredded pork with fish flavor')
dish1.eat()																		// Output: dish - fish flavor meat really delicious ~
const dish2 = Restaurant.orderDish('Braised spare Ribs') // Output: Error This store does not have this -. -
Copy the code
  • The Dish class is the abstract product class, and the subclasses that inherit it need to implement its method eat.
  • The above implementation abstracts the functional structure of the product into abstract product classes. In fact, we can go further, the factory class also use abstract class constraint, that is, abstract factory class, for example, this restaurant can cook food and soup, another restaurant can also cook food and soup, there is a common functional structure, we can use the common structure as an abstract class abstraction, as follows:
/* Restaurants can do dishes and soups
class AbstractRestaurant {
    constructor() {
        if (new.target === AbstractRestaurant)
            throw new Error('Abstract classes cannot be instantiated directly! ')
        this.signborad = 'hotel'
    }
    
    /* Create a dish */
    createDish() { throw new Error('Abstract methods cannot be called! ')}/* Create soup */
    createSoup() { throw new Error('Abstract methods cannot be called! ')}}/* Specific hotel */
class Restaurant extends AbstractRestaurant {
    constructor() { super()}createDish(type) {
        switch (type) {
            case 'Shredded pork with fish flavor':
                return new YuXiangRouSi()
            case 'Kung Pao Chicken':
                return new GongBaoJiDing()
            default:
                throw new Error('We don't have it.')}}createSoup(type) {
        switch (type) {
            case 'Nori egg soup':
                return new ZiCaiDanTang()
            default:
                throw new Error('We don't have this soup.')}}}/* Eat */
class AbstractDish {
    constructor() {
        if (new.target === AbstractDish) {
            throw new Error('Abstract classes cannot be instantiated directly! ')}this.kind = 'food'
    }
    
    /* Abstract method */
    eat() { throw new Error('Abstract methods cannot be called! ')}}/* 菜 鱼香肉丝类 */
class YuXiangRouSi extends AbstractDish {
    constructor() {
        super(a)this.type = 'Shredded pork with fish flavor'
    }
    
    eat() { console.log(this.kind + The '-' + this.type + 'really sweet ~')}}/* Kung Pao chicken */
class GongBaoJiDing extends AbstractDish {
    constructor() {
        super(a)this.type = 'Kung Pao Chicken'
    }
    
    eat() { console.log(this.kind + The '-' + this.type + 'Reminds me of grandma's cooking.')}}/* Drink */
class AbstractSoup {
    constructor() {
        if (new.target === AbstractDish) {
            throw new Error('Abstract classes cannot be instantiated directly! ')}this.kind = 'soup'
    }
    
    /* Abstract method */
    drink() { throw new Error('Abstract methods cannot be called! ')}}/* Soup nori egg soup */
class ZiCaiDanTang extends AbstractSoup {
    constructor() {
        super(a)this.type = 'Nori egg soup'
    }
    
    drink() { console.log(this.kind + The '-' + this.type + 'I drank it all my life.')}}const restaurant = new Restaurant()

const soup1 = restaurant.createSoup('Nori egg soup')
soup1.drink()																		// Output: soup - NORi egg soup I grew up drinking ~
const dish1 = restaurant.createDish('Shredded pork with fish flavor')
dish1.eat()																			// Output: dish - fish flavor meat really delicious ~
const dish2 = restaurant.createDish('Braised spare Ribs')  // Output: Error This store does not have this -. -
Copy the code

So if you create a new hotel, and the new hotel inherits the abstract hotel class, then you also implement the abstract hotel class, so that all have the structure of the abstract hotel class.

3. Generic implementation of the abstract factory pattern

Let’s extract the AbstractFactory model, restaurant or Factory, dish type is AbstractFactory, and dishes realizing abstract class are concrete products. Products realizing different abstract classes can be obtained through the Factory, and these products can be divided into class clusters according to the realized abstract class. 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:

Here is a generic implementation, skipped over in prototype mode:

/* Factory abstract class */
class AbstractFactory {
    constructor() {
        if (new.target === AbstractFactory) 
            throw new Error('Abstract classes cannot be instantiated directly! ')}/* Abstract method */
    createProduct1() { throw new Error('Abstract methods cannot be called! ')}}/* Specific hotel */
class Factory extends AbstractFactory {
    constructor() { super()}createProduct1(type) {
        switch (type) {
            case 'Product1':
                return new Product1()
            case 'Product2':
                return new Product2()
            default:
                throw new Error('Currently there is no such product -. - ')}}}/* Abstract product class */
class AbstractProduct {
    constructor() {
        if (new.target === AbstractProduct) 
            throw new Error('Abstract classes cannot be instantiated directly! ')
        this.kind = 'Abstract Product Class 1'
    }
    
    /* Abstract method */
    operate() { throw new Error('Abstract methods cannot be called! ')}}/* Specific product category 1 */
class Product1 extends AbstractProduct {
    constructor() {
        super(a)this.type = 'Product1'
    }
    
    operate() { console.log(this.kind + The '-' + this.type) }
}

/* Specific product category 2 */
class Product2 extends AbstractProduct {
    constructor() {
        super(a)this.type = 'Product2'
    }
    
    operate() { console.log(this.kind + The '-' + this.type) }
}


const factory = new Factory()

const prod1 = factory.createProduct1('Product1')
prod1.operate()																		// Output: abstract product class 1-product1
const prod2 = factory.createProduct1('Product3')	// Output: Error Currently does not have this product -. -
Copy the code
  • If you want to add products to the second class cluster, in addition to changing the corresponding factory class, you need to add an abstract product class and extend the new product on top of the abstract product class.
  • We don’t necessarily need each factory to inherit the abstract factory class in practice. For example, if there is only one factory, we can directly use the factory mode and use it flexibly in practice.

4. 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;

5. Use scenarios of abstract factory patterns

If a group of instances all have the same structure, then the abstract factory pattern can be used.