Javascript template pattern

Definition and Composition of Template Method Pattern

The Template Method pattern is a very simple pattern that can be implemented simply by using inheritance.

The template method pattern consists of two parts, the first part is the abstract parent class, and the second part is the concrete implementation subclass.Usually, the algorithm framework of the subclass is encapsulated in the abstract parent class, including implementing some public methods and encapsulating the execution order of all methods in the subclass. By inheriting this abstract class, the subclass also inherits the entire algorithm structure, and can choose to override the method of the parent class.

If we have some parallel subclasses, there are some same behaviors and some different behaviors between the subclasses. If the same and different behaviors are mixed in the implementation of each subclass, it means that these same behaviors will be repeated in each subclass. But in fact, the same behavior can be moved to another single place, and the template method pattern was born to solve this problem. In the template method pattern, the same part of the subclass implementation is moved up to the parent class, and the different parts are left for the subclass to implement. This also reflects the idea of ​​generalization very well.

First example - Coffee or Tea

Here is an example to illustrate the template pattern:

make a cup of coffee first

First of all, let's make a cup of coffee first. If there is no personal need, the steps of making coffee are usually as follows:
(1) Boil the water
(2) Brew coffee with boiling water
(3) Pour the coffee into the cup
(4) Adding sugar and milk
Through the following code, we can get a cup of fragrant coffee:

        var Coffee = function(){
    
    };
        Coffee.prototype.boilWater = function(){
    
    
            console.log( ’把水煮沸’ );
        };

        Coffee.prototype.brewCoffeeGriends = function(){
    
    
            console.log( ’用沸水冲泡咖啡’ );
        };

        Coffee.prototype.pourInCup = function(){
    
    
            console.log( ’把咖啡倒进杯子’ );
        };

        Coffee.prototype.addSugarAndMilk = function(){
    
    
            console.log( ’加糖和牛奶’ );
        };

        Coffee.prototype.init = function(){
    
    
            this.boilWater();
            this.brewCoffeeGriends();
            this.pourInCup();
            this.addSugarAndMilk();
        };

        var coffee = new Coffee();
        coffee.init();

make a pot of tea

Next, we start to prepare our tea. The steps of making tea are not much different from the steps of making coffee:
(1) Boil the water
(2) Steep the tea leaves with boiling water
(3) Pour the tea into the cup
(4) Add lemon
and the same Use a piece of code to realize the steps of making tea:

        var Tea = function(){
    
    };

        Tea.prototype.boilWater = function(){
    
    
            console.log( ’把水煮沸’ );
        };

        Tea.prototype.steepTeaBag = function(){
    
    
            console.log( ’用沸水浸泡茶叶’ );
        };

        Tea.prototype.pourInCup = function(){
    
    
            console.log( ’把茶水倒进杯子’ );
        };

        Tea.prototype.addLemon = function(){
    
    
            console.log( ’加柠檬’ );
        };

        Tea.prototype.init = function(){
    
    
            this.boilWater();
            this.steepTeaBag();
            this.pourInCup();
            this.addLemon();
        };

        var tea = new Tea();
        tea.init();

separate common ground

Now we have made a cup of coffee and a pot of tea respectively. After thinking and comparing, we found that the brewing process of coffee and tea is similar, as shown in the table.
insert image description here
We find that there are mainly the following differences between making coffee and making tea.
The ingredients are different. One is coffee and the other is tea, but we can abstract them as "drinks".
❏ The way of soaking is different. Coffee is brewed, while tea is soaked, we can abstract them all as "bubble".
❏ Different seasonings are added. One is sugar and milk, and the other is lemon, but we can abstract them all as "seasoning". After abstraction, whether it is making coffee or tea, we can organize it into the following four steps:
(1) Boil the water
(2) Brew the drink with boiling water
(3) Pour the drink into the cup
(4) Add seasoning
So, Whether brewing or soaking, we can give it a new method name, such as brew(). In the same way, whether it is adding sugar and milk, or adding lemon, we can call it addCondiments().

Let's forget about the Coffee and Tea classes we created at the beginning. Now you can create an abstract parent class to represent the entire process of making a drink. Whether it is Coffee or Tea, we use Beverage to represent it. The code is as follows:

        var Beverage = function(){
    
    };
        Beverage.prototype.boilWater = function(){
    
    
            console.log( ’把水煮沸’ );
        };

        Beverage.prototype.brew = function(){
    
    };      // 空方法,应该由子类重写

        Beverage.prototype.pourInCup = function(){
    
    };    // 空方法,应该由子类重写

        Beverage.prototype.addCondiments = function(){
    
    };    // 空方法,应该由子类重写

        Beverage.prototype.init = function(){
    
    
            this.boilWater();
            this.brew();
            this.pourInCup();
            this.addCondiments();
        };

Create a Coffee subclass and a Tea subclass

It is meaningless for us to create an object of the Beverage class now, because there is no real drink called "beverage" in the world, and the drink is only an abstract existence here. Next we need to create the coffee and tea classes and let them inherit the beverage class:

        var Coffee = function(){
    
    };
        Coffee.prototype = new Beverage();

Next, we need to rewrite some methods in the abstract parent class. Only the behavior of "boiling water" can directly use the boilWater method in the parent class Beverage. Other methods need to be rewritten in the Coffee subclass. The code is as follows:

        Coffee.prototype.brew = function(){
    
    
            console.log( ’用沸水冲泡咖啡’ );
        };

        Coffee.prototype.pourInCup = function(){
    
    
            console.log( ’把咖啡倒进杯子’ );
        };
        Coffee.prototype.addCondiments = function(){
    
    
            console.log( ’加糖和牛奶’ );
        };

        var Coffee = new Coffee();
        Coffee.init();

So far, our Coffee class has been completed. When calling the init method of the coffee object, since there is no corresponding init method on the prototype prototype of the coffee object and the Coffee constructor, the request will be entrusted to Coffee along the prototype chain. The init method on the "parent" Beverage prototype.

The Beverage.prototype.init method has already stipulated the order of making drinks, so we can successfully make a cup of coffee. The code is as follows:

        Beverage.prototype.init = function(){
    
    
            this.boilWater();
            this.brew();
            this.pourInCup();
            this.addCondiments();
        };

Create the Tea class in the same way

        var Tea = function(){
    
    };

        Tea.prototype = new Beverage();

        Tea.prototype.brew = function(){
    
    
            console.log( ’用沸水浸泡茶叶’ );
        };

        Tea.prototype.pourInCup = function(){
    
    
            console.log( ’把茶倒进杯子’ );
        };

        Tea.prototype.addCondiments = function(){
    
    
            console.log( ’加柠檬’ );
        };

        var tea = new Tea();
        tea.init();

hook method

Through the template method pattern, we encapsulate the algorithm framework of the subclass in the parent class. These algorithm frameworks are applicable to most subclasses under normal conditions, but what if there are some special "personality" subclasses?

But some guests drink coffee without flavoring (sugar and milk). Since Beverage, as the parent class, has stipulated the four steps of brewing beverages, is there any way to make subclasses not subject to this constraint?

Hooks can be used to solve this problem, and placing hooks is a common means of isolating changes. We place hooks in places that are easy to change in the parent class. The hook can have a default implementation. It is up to the subclass to decide whether to "hook" or not. The return result of the hook method determines the execution steps of the latter part of the template method, that is, the next direction of the program. In this way, the program has the possibility of changing.

In this example, we set the name of the hook as customerWantsCondiments, and then put the hook into the Beverage class to see how we can get a cup of coffee without sugar and milk. The code is as follows:

        var Beverage = function(){
    
    };

        Beverage.prototype.boilWater = function(){
    
    
            console.log( ’把水煮沸’ );
        };

        Beverage.prototype.brew = function(){
    
    
            throw new Error( ’子类必须重写brew方法’ );
        };

        Beverage.prototype.pourInCup = function(){
    
    
            throw new Error( ’子类必须重写pourInCup方法’ );
        };

        Beverage.prototype.addCondiments = function(){
    
    
            throw new Error( ’子类必须重写addCondiments方法’ );
        };

        Beverage.prototype.customerWantsCondiments = function(){
    
    
            return true;    // 默认需要调料
        };

        Beverage.prototype.init = function(){
    
    
            this.boilWater();
            this.brew();
            this.pourInCup();
            if ( this.customerWantsCondiments() ){
    
        // 如果挂钩返回true,则需要调料
              this.addCondiments();
            }
        };

        var CoffeeWithHook = function(){
    
    };

        CoffeeWithHook.prototype = new Beverage();

        CoffeeWithHook.prototype.brew = function(){
    
    
            console.log( ’用沸水冲泡咖啡’ );
        };

        CoffeeWithHook.prototype.pourInCup = function(){
    
    
            console.log( ’把咖啡倒进杯子’ );
        };

        CoffeeWithHook.prototype.addCondiments = function(){
    
    
            console.log( ’加糖和牛奶’ );
        };

        CoffeeWithHook.prototype.customerWantsCondiments = function(){
    
    
            return window.confirm( ’请问需要调料吗?' );
        };

        var coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.init();

Is "inheritance" really needed?

        var Beverage = function( param ){
    
    

            var boilWater = function(){
    
    
              console.log( ’把水煮沸’ );
            };

            var brew = param.brew || function(){
    
    
              throw new Error( ’必须传递brew方法’ );
            };

            var pourInCup = param.pourInCup || function(){
    
    
              throw new Error( ’必须传递pourInCup方法’ );
            };

            var addCondiments = param.addCondiments || function(){
    
    
              throw new Error( ’必须传递addCondiments方法’ );
            };

            var F = function(){
    
    };

            F.prototype.init = function(){
    
    
              boilWater();
              brew();
              pourInCup();
              addCondiments();
            };

            return F;
        };

        var Coffee = Beverage({
    
    
            brew: function(){
    
    
              console.log( ’用沸水冲泡咖啡’ );
            },
            pourInCup: function(){
    
    
                console.log( ’把咖啡倒进杯子’ );
            },
            addCondiments: function(){
    
    
                console.log( ’加糖和牛奶’ );
            }
        });
        var Tea = Beverage({
    
    
            brew: function(){
    
    
              console.log( ’用沸水浸泡茶叶’ );
            },
            pourInCup: function(){
    
    
                console.log( ’把茶倒进杯子’ );
            },
            addCondiments: function(){
    
    
                console.log( ’加柠檬’ );
            }
        });

        var coffee = new Coffee();
        coffee.init();

        var tea = new Tea();
        tea.init();

In this code, we pass the brew, pourInCup, and addCondiments methods into the Beverage function in turn, and the Beverage function returns to the constructor F after being called. The F class contains the "template method" F.prototype.init. Same as the effect obtained by inheritance, the "template method" still encapsulates the algorithm framework of the beverage subclass.

Guess you like

Origin blog.csdn.net/weixin_45172119/article/details/128716894