In depth understanding of the JavaScript series (40) : detailed explanation of composite design patterns

  • 2020-05-10 17:38:21
  • OfStack

introduce

The composite pattern (Composite) combines objects into a tree structure to represent a "part-whole" hierarchy, and the composite pattern makes the user's use of individual objects and composite objects one-or-one.

Common scenarios include the control mechanism in asp.net (that is, control can contain children control and can recursively operate, add and delete children control), and the similar mechanism in DOM, where one DOM node can contain child nodes, and both parent nodes and child nodes have the common function of adding, deleting and traversing child nodes. So the key to a composite pattern is to have an abstract class that can represent both child and parent elements.

The body of the

For example, a restaurant offers a wide variety of dishes, each table has this menu, the menu is a list of the restaurant dishes, accidentally by a pastry for breakfast, lunch, dinner, etc., every meal has a variety of menu items, assuming that whether a menu item or the entire menu should be printed, and you can add items, such as lunch can add new dishes, and menu items can also add sugar or coffee.

In this case, we can use composition to represent the content as a hierarchy. Let's break down our implementation steps 1 by 1.

Step 1: implement our "abstract class" function MenuComponent:


var MenuComponent = function () {
};
MenuComponent.prototype.getName = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.getDescription = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.getPrice = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.isVegetarian = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.print = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.add = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.remove = function () {
    throw new Error(" The method must be overwritten !");
};
MenuComponent.prototype.getChild = function () {
    throw new Error(" The method must be overwritten !");
};

This function provides two types of methods, one is to get information, such as price, name, etc., and the other is a general operation method, such as print, add, delete, get submenu.

Step 2: create the basic menu items:


var MenuItem = function (sName, sDescription, bVegetarian, nPrice) {
    MenuComponent.apply(this);
    this.sName = sName;
    this.sDescription = sDescription;
    this.bVegetarian = bVegetarian;
    this.nPrice = nPrice;
};
MenuItem.prototype = new MenuComponent();
MenuItem.prototype.getName = function () {
    return this.sName;
};
MenuItem.prototype.getDescription = function () {
    return this.sDescription;
};
MenuItem.prototype.getPrice = function () {
    return this.nPrice;
};
MenuItem.prototype.isVegetarian = function () {
    return this.bVegetarian;
};
MenuItem.prototype.print = function () {
    console.log(this.getName() + ": " + this.getDescription() + ", " + this.getPrice() + "euros");
};

As can be seen from the code, we only re-used the prototype of four methods to obtain information and print method, but did not overload the other three operation methods, because the basic dishes do not include adding, deleting and obtaining sub-dishes.

Step 3: create dishes


var Menu = function (sName, sDescription) {
    MenuComponent.apply(this);
    this.aMenuComponents = [];
    this.sName = sName;
    this.sDescription = sDescription;
    this.createIterator = function () {
        throw new Error("This method must be overwritten!");
    };
};
Menu.prototype = new MenuComponent();
Menu.prototype.add = function (oMenuComponent) {
    // Add sub-dishes
    this.aMenuComponents.push(oMenuComponent);
};
Menu.prototype.remove = function (oMenuComponent) {
    // Delete sub-dishes
    var aMenuItems = [];
    var nMenuItem = 0;
    var nLenMenuItems = this.aMenuComponents.length;
    var oItem = null;     for (; nMenuItem < nLenMenuItems; ) {
        oItem = this.aMenuComponents[nMenuItem];
        if (oItem !== oMenuComponent) {
            aMenuItems.push(oItem);
        }
        nMenuItem = nMenuItem + 1;
    }
    this.aMenuComponents = aMenuItems;
};
Menu.prototype.getChild = function (nIndex) {
    // Gets the specified submenu
    return this.aMenuComponents[nIndex];
};
Menu.prototype.getName = function () {
    return this.sName;
};
Menu.prototype.getDescription = function () {
    return this.sDescription;
};
Menu.prototype.print = function () {
    // Print current dishes and all sub-dishes
    console.log(this.getName() + ": " + this.getDescription());
    console.log("--------------------------------------------");     var nMenuComponent = 0;
    var nLenMenuComponents = this.aMenuComponents.length;
    var oMenuComponent = null;     for (; nMenuComponent < nLenMenuComponents; ) {
        oMenuComponent = this.aMenuComponents[nMenuComponent];
        oMenuComponent.print();
        nMenuComponent = nMenuComponent + 1;
    }
};

Note the above code, in addition to the implementation of adding, deleting and obtaining methods, the printing print method is to first print the current dish information, and then loop through the printing of all sub-dish information.

Step 4: create the specified dishes:

We can create several real dishes, such as dinner, coffee, pastry, etc., which are all based on Menu. The code is as follows:


var DinnerMenu = function () {
    Menu.apply(this);
};
DinnerMenu.prototype = new Menu(); var CafeMenu = function () {
    Menu.apply(this);
};
CafeMenu.prototype = new Menu(); var PancakeHouseMenu = function () {
    Menu.apply(this);
};
PancakeHouseMenu.prototype = new Menu();

Step 5. Create the top-level menu container -- menu book:

var Mattress = function (aMenus) {
    this.aMenus = aMenus;
};
Mattress.prototype.printMenu = function () {
    this.aMenus.print();
};

This function takes an array of menus as an argument, and the value provides the printMenu method for printing all the menu contents.

Step 6. Method of invocation:


var oPanCakeHouseMenu = new Menu("Pancake House Menu", "Breakfast");
var oDinnerMenu = new Menu("Dinner Menu", "Lunch");
var oCoffeeMenu = new Menu("Cafe Menu", "Dinner");
var oAllMenus = new Menu("ALL MENUS", "All menus combined"); oAllMenus.add(oPanCakeHouseMenu);
oAllMenus.add(oDinnerMenu); oDinnerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));
oDinnerMenu.add(oCoffeeMenu); oCoffeeMenu.add(new MenuItem("Express", "Coffee from machine", false, 0.99)); var oMattress = new Mattress(oAllMenus);
console.log("---------------------------------------------");
oMattress.printMenu();
console.log("---------------------------------------------");

For those of you familiar with the development of asp.net controls, do they look familiar?

conclusion

The usage scenarios for the composite pattern are clear:

When you want to represent the object's part-whole hierarchy;
You want the user to ignore the difference between composite objects and individual objects, and the user will use all the objects (methods) in the composite structure.
In addition, this pattern is often used with decorator 1, which usually has a common parent class (that is, the stereotype), so the decorator must support the component interface with add, remove, getChild operations.


Related articles: