The code has been written for several years, and the design pattern is in a state of forgetting and forgetting. Recently, I have some feelings about the design pattern, so I will learn and summarize it again.

Most speak design pattern articles are using Java, c + + such based on the class the static type of language, as a front-end developer, js this prototype based dynamic language, function has become a first class citizen, slightly different on some design patterns, even simple to don’t like to use the design patterns, sometimes also can produce some confusion.

The following are summarized in the order of “scenario” – “Design pattern definition” – “code implementation” – “Mixable design pattern” – “total”. If there are any inappropriate points, welcome to discuss.

scenario

Wechat applets define a Page through the Page method provided by wechat, and then pass in a configuration object.

Page({
  data: { // Data for participating in page rendering
    logs: []},onLoad: function () {
    // execute after the page is rendered}})Copy the code

Let’s say we have a requirement to report some custom data every time a page loads.

Of course, the most direct is to go to each page to add, but the logic of reporting data is consistent, one by one add a little silly, here can use the decorator mode.

Decorator mode

Take a look at the Wikipedia definition.

Decorator pattern is a design pattern that dynamically adds new behavior to a category in the object-oriented programming domain. In terms of functionality, decorated patterns are more flexible than generating subcategories, so you can add functionality to an object rather than the entire category.

Take a look at the UML class diagram and sequence diagram.

When the operation method in Component1 is accessed, the operation methods in Decorator1 and Decorator2, both predefined decorators, are called, perform some additional operations, and the original operation method is executed.

Here’s a simple example:

If you buy milk tea, you can add pearls, coconuts, etc. Different ingredients have different prices or can be freely combined. At this time, you can use the decorator mode to add ingredients and calculate the price of the original milk tea.

The original milk tea has an interface and class.

interface MilkTea {
    public double getCost(a); // Return the price of milk tea
    public String getIngredients(a); // Return the raw material of milk tea
}

class SimpleMilkTea implements MilkTea {
    @Override
    public double getCost(a) {
        return 10;
    }

    @Override
    public String getIngredients(a) {
        return "MilkTea"; }}Copy the code

Below the introduction of decorative, feeding.

// Add a decorator abstract class
abstract class MilkTeaDecorator implements MilkTea {
    private final MilkTea decoratedMilkTea;

    public MilkTeaDecorator(MilkTea c) {
        this.decoratedMilkTea = c;
    }

    @Override
    public double getCost(a) {
        return decoratedMilkTea.getCost();
    }

    @Override
    public String getIngredients(a) {
        returndecoratedMilkTea.getIngredients(); }}// Add a pearl
class WithPearl extends MilkTeaDecorator {
    public WithPearl(MilkTea c) {
        super(c); // Call the parent constructor
    }

    @Override
    public double getCost(a) { 
        // Call the parent method
        return super.getCost() + 2;
    }

    @Override
    public String getIngredients(a) {
       // Call the parent method
        return super.getIngredients() + "Add pearls."; }}// Add coconut
class WithCoconut extends MilkTeaDecorator {
    public WithCoconut(MilkTea c) {
        super(c);
    }

    @Override
    public double getCost(a) {
        return super.getCost() + 1;
    }

    @Override
    public String getIngredients(a) {
        return super.getIngredients() + "Add coconut."; }}Copy the code

Let’s test that out,

public class Main {
    public static void printInfo(MilkTea c) {
        System.out.println("Price:" + c.getCost() + "; Charging:" + c.getIngredients());
    }

    public static void main(String[] args) {
        MilkTea c = new SimpleMilkTea();
        printInfo(c); // Price: 10.0; Charging: MilkTea
        
        c = new WithPearl(new SimpleMilkTea());
        printInfo(c); // Price: 12.0; Fillings: MilkTea, with pearls
        
        c = new WithCoconut(new WithPearl(new SimpleMilkTea()));
        printInfo(c); // Price: 13.0; Fillings: MilkTea, pearls, coconut}}Copy the code

In the future, if you need to add a small material, just write a new decorator class, and can be matched with the previous small material.

// Add ice cream
class WithCream extends MilkTeaDecorator {
    public WithCream(MilkTea c) {
        super(c);
    }

    @Override
    public double getCost(a) {
        return super.getCost() + 5;
    }

    @Override
    public String getIngredients(a) {
        return super.getIngredients() + "With ice cream."; }}public class Main {
    public static void printInfo(MilkTea c) {
        System.out.println("Price:" + c.getCost() + "; Charging:" + c.getIngredients());
    }

    public static void main(String[] args) {
        c = new WithCoconut(new WithCream(new WithPearl(new SimpleMilkTea())));
        printInfo(c); // Price: 18.0; Ingredients: MilkTea, with pearls, with ice cream, with coconut}}Copy the code

Let’s rewrite it with JS to get the same effect.

const SimpleMilkTea = () = > {
    return {
        getCost() {
            return 10;
        },

        getIngredients() {
            return "MilkTea"; }}; };/ / add pearls
const WithPearl = (milkTea) = > {
    return {
        getCost() {
            return milkTea.getCost() + 2;
        },

        getIngredients() {
            return milkTea.getIngredients() + "Add pearls."; }}; };/ / add coconut
const WithCoconut = (milkTea) = > {
    return {
        getCost() {
            return milkTea.getCost() + 1;
        },

        getIngredients() {
            return milkTea.getIngredients() + "Add coconut."; }}; };// Add ice cream
const WithCream = (milkTea) = > {
    return {
        getCost() {
            return milkTea.getCost() + 5;
        },

        getIngredients() {
            return milkTea.getIngredients() + "With ice cream."; }}; };// test
const printInfo = (c) = > {
    console.log(
        "Price:" + c.getCost() + "; Charging:" + c.getIngredients()
    );
};

let c = SimpleMilkTea();
printInfo(c); // Price: 10; Charging: MilkTea

c = WithPearl(SimpleMilkTea());
printInfo(c); // Price: 12; Fillings: MilkTea, with pearls

c = WithCoconut(WithPearl(SimpleMilkTea()));
printInfo(c); // Price: 13; Fillings: MilkTea, pearls, coconut

c = WithCoconut(WithCream(WithPearl(SimpleMilkTea())));
printInfo(c); // Price: 18; Ingredients: MilkTea, with pearls, with ice cream, with coconut

Copy the code

Instead of defining classes and interfaces, JS is represented directly by functions.

The original SimpleMilkTea method returned a milk tea object, and then defined three decorator functions that passed in a milk tea object and returned a decorated object.

Code implementation

Going back to the scenario at the beginning of this article, we need to report some custom data for each page load. All we need to do is introduce a decorator function that decorates the option passed in and returns it.

const Base = (option) = > {
  const{ onLoad ... rest } = option;return {
    ...rest,
    // Override the onLoad method
    onLoad(. args) { 
      // Add a routing field
      this.reportData(); // Report data

      // onLoad
      if (typeof onLoad === 'function') {
        onLoad.call(this. args); }}reportData() {
      // Do something}}Copy the code

Then go back to the original page and add the Base call.

Page(Base({
  data: { // Data for participating in page rendering
    logs: []},onLoad: function () {
    // execute after the page is rendered}})Copy the code

Similarly, with the decorator pattern we can uniformly plug in what we need to do to other life cycles without the business side having to write it all over again.

On a large team, each business side might need to do something during the mini-program life cycle, where you simply take advantage of the decorator pattern, write a decorator function, and call it in the business code.

The final business code may decorate many layers before being passed to the applets Page function.

Page(Base(Log(Ce({
  data: { // Data for participating in page rendering
    logs: []},onLoad: function () {
    // execute after the page is rendered}})Copy the code

Mixable design patterns

If you have seen the proxy pattern before, this may be a little confusing, because much like the proxy pattern, it wraps and enhances existing objects.

But there are big differences:

In the proxy mode, we directly encapsulate the original object into the proxy object, for the business side does not care about the original object, directly use the proxy object.

In decorator mode, we only provide decorator functions that input primitive objects and output enhanced objects. The output enhancement object can then be passed into a new decorator function for further enhancement. For the business side, you can combine decorator functions any way you want, but you need to have a primitive object to start with.

To be more specific:

In proxy mode, the dependencies between objects are already written out. Add proxy object A1 to original object A, and then add proxy object A2 to A1. If we do not want the functionality added to A1, we cannot use A2 directly, because A2 already contains the functionality of A1. We can only write A new proxy object A3 on the basis of A.

For decorator mode, we only provide the decorator function A1, the decorator function A2, and then decorates the original object A2(A1(A)). If you don’t want the functionality added to A1, simply remove the decorator A1 and call A2(A).

So using the proxy pattern or using the decorator pattern depends on whether we want to wrap up all the functionality and eventually produce one object for the business to use, or provide a lot of functionality and let the business combine it freely.

The total

The decorator pattern also implements the “single responsibility principle” to separate the functions of objects/functions and reduce coupling between them.

In business development, if an object/function has too much functionality, consider using the decorator pattern to split it.