This is the seventh day of my participation in the August More text Challenge. For details, see: August More Text Challenge

In programming, there is something similar to the idea that things are made of similar subthings. The combinatorial pattern is to build larger objects out of smaller child objects, which may themselves be made up of smaller “grandchildren.”

Review macro commands

The structure and function of macro commands are mentioned in command mode. A macro command object contains a set of specific subcommand objects. In either case, there is an execute method responsible for executing the command. Now review the macro command code in command mode:

var closeDoorCommand = {
  execute: function () {
    console.log('shut down'); }};var openPcCommand = {
  execute: function () {
    console.log('Turn on the computer'); }};var openQQCommand = {
  execute: function () {
    console.log('login QQ'); }};var MacroCommand = function () {
  return {
    commandsList: [].add: function (command) {
      this.commandsList.push(command);
    },
    execute: function () {
      for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); }}}};var macroCommand = MacroCommand();
macroCommand.add(closeDoorCommand);
macroCommand.add(openPcCommand);
macroCommand.add(openQQCommand);
macroCommand.execute();
Copy the code

As you can see, the macro command contains a set of subcommands that form a tree, albeit a very simple tree.Among them,macroCommandIt’s called a composite object,closeDoorCommand,openPcCommand,openQQCommandBoth are leaf objects. inmacroCommandexecuteMethod, instead of performing the actual operation, it traverses the leaf objects it contains, turning the realexecuteRequests delegate to these leaf objects.

MacroCommand behaves like a command, but it’s really just a “proxy” for a set of real commands. MacroCommand is not a true proxy. Although similar in structure, macroCommand is only responsible for passing requests to leaf objects. Its purpose is not to control access to leaf objects.

Purpose of composite patterns

1 represents the tree structure

In the example above, it is clear that the combinatorial pattern has one advantage: it provides a way to traverse the tree structure. By calling the execute method on the combinatorial object, the program recursively calls the Execute method on the leaf objects below the combinatorial object. The composite pattern is a very convenient way to describe parts of an object — the overall hierarchy.

2. Use object polymorphism to treat combined objects and single objects uniformly

The polymorphic representation of an object enables the client to ignore the differences between a composite object and a single object. In the composite pattern, customers use all objects in the composite structure uniformly, regardless of whether it is a composite object or a single object.

The process of requesting passage through the tree

In the composite pattern, the process of passing a request through the tree always follows a logic.

In the case of a macro command, the request is passed down from the object at the top of the tree. If the object currently processing the request is a leaf object (ordinary subcommand), the leaf object itself will process the request accordingly. If the object currently processing the request is a composite object (macro command), the composite object traverses its children under the tree, passing the request on to them.

Requests are passed up and down the tree until they reach the end of the tree. As a user, you only need to care about the composite object at the top of the tree, you just need to request that composite object, and the request will be passed down the tree, reaching all the leaf objects at once.

More powerful macro commands

MacroCommand currently includes three commands: close the door, open the computer and log in QQ. Now we need a “super universal remote control”, which can control all the appliances in the home. This remote control has the following functions:

  • Turn on the air conditioning
  • Turn on the TV and stereo
  • Close the door, open the computer and log in QQ

First, place a button in the node to represent the super universal remote control.

<button id=button> press me </button><script>
  var MacroCommand = function () {
    return {
      commandsList: [].add: function (command) {
        this.commandsList.push(command);
      },
      execute: function () {
        for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); }}}};var openAcCommend = {
    execute: function () {
      console.log('Turn on the air conditioner'); }}// Turn on the TV and stereo
  var openTvCommand = {
    execute: function () {
      console.log('Turn on the TV'); }}var openSoundCommand = {
    execute: function () {
      console.log('Turn on the stereo'); }}var macroCommand1 = MacroCommand()
  macroCommand1.add(openTvCommand)
  macroCommand1.add(openSoundCommand)

  // Close the door, open the computer, the QQ command
  var closeDoorCommand = {
    execute: function () {
      console.log('shut down'); }};var openPcCommand = {
    execute: function () {
      console.log('Turn on the computer'); }};var openQQCommand = {
    execute: function () {
      console.log('login QQ'); }};var macroCommand2 = MacroCommand();
  macroCommand2.add(closeDoorCommand);
  macroCommand2.add(openPcCommand);
  macroCommand2.add(openQQCommand);

  // Combine all commands into one super command
  var macroCommand = MacroCommand();
  macroCommand.add(openAcCommend)
  macroCommand.add(macroCommand1)
  macroCommand.add(macroCommand2)

  // Bind the command to the super remote
  var setCommand = (function (command) {
    document.getElementById('button').onclick = function () {
      command.execute()
    }
  })(macroCommand)
</script>
Copy the code

When the button of the remote control is pressed, all the commands are executed in turn, as shown in the figure:

Transparency brings security issues

The transparency of the composite pattern allows the requesting customer not to worry about the difference between composite objects and leaf objects in the tree, but they are fundamentally different.

Composite objects can have leaf nodes, and there are no children under leaf objects, so we might have some misoperations like trying to add child nodes to leaf objects. The solution is to add the add method to the leaf object as well, and throw an exception to alert the user when the method is called.

var MacroCommand = function () {
  return {
    commandsList: [].add: function (command) {
      this.commandsList.push(command);
    },
    execute: function () {
      for (var i = 0, command; command = this.commandsList[i++];) { command.execute(); }}}};var openAcCommend = {
  execute: function () {
    console.log('Turn on the air conditioner');
  },
  add: function() {
    throw new Error('Leaf objects cannot add child nodes')}}Copy the code

A couple of things to note

1 The combination pattern is not a parent-child relationship

The composite pattern IS A has-a (aggregation) relationship, not is-A. A Composite object contains a set of Leaf objects, but Leaf is not a subclass of Composite. A composite object delegates requests to all the leaf objects it contains, and the key to their cooperation is to have the same interface.

For the sake of description, the parent and parent objects are sometimes referred to as parent nodes, but they are not really parent.

Consistency of operations on leaf objects

In addition to the requirement that the composite object and the leaf object have the same interface, another requirement of the composition pattern is that the operation on a set of leaf objects must be consistent.

3 Bidirectional mapping

If the combined object is not a single leaf object dependencies, you will need to give and composite object to establish bidirectional mapping relations, a simple solution is to give both sides set to hold the reference of the other party, but the mutual reference is very complex, and produced too much coupling between objects, modify, or delete an object becomes difficult, at this time, We can introduce a mediator pattern to manage these objects.

Improve composite pattern performance with chain of responsibility pattern

In composite mode, if the structure of the tree is complex and the number of nodes is large, the performance of traversing the tree may be less than ideal. One way to avoid traversing the entire tree in practice is to use the chain of responsibility pattern. The chain of responsibility pattern usually requires us to set the chain manually, but in the composite pattern, there is actually a natural chain of responsibility between parent and child objects. One of the classic scenarios of the chain of responsibility pattern is to have a request go from parent to child, or vice versa, from child to parent, until an object that can handle the request is encountered.

When to use composite patterns

1 represents the part of the object — the whole hierarchy

The composite pattern makes it easy to construct a tree to represent the partial — overall structure — of an object. Especially when we weren’t sure how many layers the tree had during development. After the construction of the tree is finally completed, it is possible to perform unified operations on the whole tree by simply requesting the topmost object of the tree. Adding and removing nodes to a tree in a composite pattern is convenient and follows the open-close principle.

2 The customer wants to treat all objects in the tree uniformly

The combinatorial pattern allows the customer to ignore the difference between a combinatorial object and a leaf object. When the customer is faced with the tree, he does not care whether the object he is currently working with is a combinatorial object or a leaf object, and he does not have to write a bunch of if and else statements to process them separately. Composition objects and leaf objects each do their own right thing, which is the most important ability of composition patterns.

summary

The composite pattern allows us to create the structure of an object using a tree. We can apply the same operations to both composite objects and individual objects. In most cases, we can ignore the differences between composite objects and individual objects and treat them in a consistent way.

However, the composite pattern is not perfect and can result in a system in which every object looks like every other object. The differences only become apparent when they are run, making the code difficult to understand. In addition, if too many objects are created through the composite pattern, these objects may become unaffordable to the system.

One last word

If this article is helpful to you, or inspired, please help to like and follow it, your support is the biggest motivation for me to keep writing, thank you for your support.