preface

As a design principle mentioned in the previous article — programming for interfaces, not implementations — one must ask, isn’t instantiating an object programming for implementations? Indeed, instantiating this activity often creates “coupling” problems, so instantiation activities should not always be done openly. So how can development be more loosely coupled? That’s the topic of this article — the Factory model.

Simple Factory model

Suppose you were making a pizza ordering system, your code might look something like this.

function orderPizza () {
    let pizza = new Pizza();
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}
Copy the code

But when you need more pizza types, you need to add some code to decide which pizza to make, so you add a code to decide which pizza to make.

function orderPizza (type) {
    let pizza;
    if (type= = ='a') {
       pizza = new aPizza(); 
    } else if(type= = ='b') {
        pizza = new bPizza(); 
    ) else{ pizza = new cPizza(); }... }Copy the code

However, there are not that many pizza types, and over time, categories can be removed, so according to the design principle mentioned earlier — classes should be open for extension and closed for design, so we need to encapsulate what does and does not change.

class PizzaStore {
    factory = null;
    constructor (factory) {
        this.factory = factory;
    }
    orderPizza (type) {
        let pizza = this.factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        returnpizza; }}class SimplePizzaFactory {
    createPizza (type) {
        let pizza;
        if (type === 'a') {
           pizza = new aPizza(); 
        } else if(type === 'b') {
            pizza = new bPizza(); 
        ) else {
            pizza = new cPizza(); 
        }
        returnpizza; }}Copy the code

By taking the pizza creation logic out of the equation, we get a method for producing objects, which we call factories. Once we have a factory, orderPizza doesn’t care about the specific production process of the object, just need you to return a pizza to me. What else is there to gain from this? First of all, the factory method is reusable. It can be used not only in orderPizza, but in any place where objects need to be produced, and only the factory needs to be modified.

The above approach is actually called a simple factory, which is more of a programming habit than a design pattern, but it is used so often that some developers do misinterpret it as a factory pattern. However, it has to be said that although it is not one of the modes, it is enough. There is no need to apply various design modes in development all the time. In fact, we only need a certain degree of “perfection”.

So what is the real factory model?

The factory pattern

The factory method pattern defines an interface for creating objects, but it is up to subclasses to decide which class to instantiate. The factory method lets classes defer instantiation to subclasses.

Suppose pizza is now opened in different regions, so there will be all kinds of variations in flavor. So what if we use the simple factory pattern above? First write three different factories to inherit SimplePizzaFactory, rewrite createPizza method, respectively is aFactory, bFactory, cFactory.

aStore = new PizzaStore(new aFactory());
aStore.orderPizza('a');

bStore = new PizzaStore(new bFactory());
bStore.orderPizza('b');
Copy the code

This seems fine, but you might find that someone will stop using the PizzaStore you set and start using their own process, causing the specification to get lost. Therefore, we need to build a better framework to make it more flexible, so here we need to use this mode – factory mode to achieve.

So all you have to do is put the createPizza method in PizzaStore, but make it an “abstract method” and subclass it for the different branches. Let’s start with pizzaStore.

class PizzaStore {
    orderPizza(type){
        let pizza;
        pizza = this.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    creatPizza () {} // Abstract methods
}
Copy the code

Now it’s up to the PizzaStore subclasses to define their own createPizza methods, so that we can implement them in a flexible way, but within the framework we’ve set. Let’s implement a branch code.

class aPizzaStore extends PizzaStore {
    creatPizza (type) {
        let pizza;
        if (type === 'a') {
           pizza = new aPizza(); 
        } else if(type === 'b') {
            pizza = new bPizza(); 
        ) else {
            pizza = new cPizza(); 
        }
        returnpizza; }}Copy the code

You can see that the pizza in orderPizza executes the various methods without knowing which concrete class is involved; in other words, this is “decoupling.”

conclusion

The factory method pattern encapsulates instantiations of specific types, and the createPizza method for creating objects provided in pizzaStore is also called a factory method. There may be other methods in the pizzaStore class that use this method, but only subclasses actually implement the method and instantiate the object.

One might still be confused by the difference between a simple factory and a factory model, although they do look very similar, but a simple factory is a way to get everything done in one place. The factory pattern, on the other hand, sets up a framework and lets subclasses decide the implementation. But a simple factory does not have the flexibility of a factory approach because a simple factory cannot change the product being created.