3.1 Mode Motivation
In real life, there are many "part-whole" relationships, such as departments and colleges in universities, departments and subsidiaries in headquarters, books and bags in study supplies, moon buns and wardrobes in household appliances, pots and pans in kitchens, etc.This is also true in software development, such as files and folders in the file system, simple controls in form programs, container controls, and so on.The processing of these simple and composite objects is very convenient if they are implemented in a combination mode.
For a specific example, the following chart is an organizational chart of a company. There are many subsidiaries under the headquarters, and there are also departments under the headquarters and departments under the subsidiaries.If you develop an OA system for such a company, how do you design it as a programmer?Don't talk about how to design an implementation first, then look down and finish what's below, then look back and think about how to design such an OA system.
Definition and characteristics of 3.2 mode
Definition of a Composite pattern: Sometimes referred to as a Part-Whole pattern, it is a pattern of grouping objects into a tree-like hierarchy that represents a "Part-Whole" relationship and provides users with consistent access to individual objects and grouped objects.Basic objects can be grouped into larger objects, and this operation is repeatable. Repeatedly, you can get a very large grouped object, but these grouped objects have the same interface with the basic object, so the grouping is transparent and consistent in usage.
The main advantages of the combination mode are:
1. Combination mode simplifies client code by enabling client code to consistently process individual and combined objects, regardless of whether they are dealing with individual or combined objects.
2. It is easier to add new objects to the body of a group. Clients will not change the source code to satisfy the Open and Close Principle by adding new objects.
The main drawbacks are:
1. Complex design, clients need to spend more time clarifying hierarchical relationships between classes;
2. It is not easy to restrict the components in the container;
3. It is not easy to use inherited methods to add new functionality to components;
Structure and implementation of 3.3 mode
Structure of 3.3.1 mode
The combination mode contains the following main roles.
1. Component role: Its main purpose is to declare common interfaces for leaf and branch components and to implement their default behavior.In transparent composition mode, abstract components also declare interfaces to access and manage subclasses; in secure composition mode, interfaces to access and manage subclasses are not declared, and management is done by branch components.
2. Leaf role: is a leaf node object in a combination that has no child nodes and is used to implement the public interfaces declared in the abstract component role.
3. Composite role: is a branch node object in a combination that has child nodes.It implements interfaces declared in abstract component roles and its primary role is to store and manage subparts, usually including Add(), Remove(), GetChild().
Combination modes are divided into transparent and safe combination modes.
(1) Transparency: In this way, since the abstract component declares all methods in all subclasses, the client does not have to distinguish between leaf and branch objects, which is transparent to the client.The disadvantage is that leaf components do not have Add(), Remove(), and GetChild() methods but implement them (empty implementation or throw exceptions), which can cause some security problems.Its structure diagram is shown in the diagram.
(2) Security method: In this way, the method of managing sub-components is moved to the branch component. Abstract components and leaf components have no management method for sub-objects, which avoids the security problem of the previous method. However, because leaves and branches have different interfaces, clients lose transparency when calling because they need to know the existence of leaf and branch objects.The structure diagram is shown in Figure 2.
Implementation of 3.3.1 Mode
Implementation code for transparent combinatorial mode
/**Includes*********************************************************************/ #include <iostream> #include <vector> /**namespace********************************************************************/ using namespace std; // An abstract part class describes the behavior that all parts will share in the future class Component { public: Component(string name) : m_strCompname(name){} virtual ~Component(){} virtual void Operation() = 0; virtual void Add(Component *) = 0; virtual void Remove(Component *) = 0; virtual Component *GetChild(int) = 0; virtual string GetName() { return m_strCompname; } virtual void Print() = 0; protected: string m_strCompname; }; class Leaf : public Component { public: Leaf(string name) : Component(name) {} void Operation() { cout<<"I'm "<< m_strCompname <<endl; } void Add(Component *pComponent){} void Remove(Component *pComponent){} Component *GetChild(int index) { return NULL; } void Print(){} }; class Composite : public Component { public: Composite(string name) : Component(name) {} ~Composite() { vector<Component *>::iterator it = m_vecComp.begin(); while (it != m_vecComp.end()) { if (*it != NULL) { cout<<"----delete "<<(*it)->GetName()<<"----"<<endl; delete *it; *it = NULL; } m_vecComp.erase(it); it = m_vecComp.begin(); } } void Operation() { cout<<"I'm "<<m_strCompname<<endl; } void Add(Component *pComponent) { m_vecComp.push_back(pComponent); } void Remove(Component *pComponent) { for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it) { if ((*it)->GetName() == pComponent->GetName()) { if (*it != NULL) { delete *it; *it = NULL; } m_vecComp.erase(it); break; } } } Component *GetChild(int index) { if (index > m_vecComp.size()) { return NULL; } return m_vecComp[index - 1]; } void Print() { for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it) { cout<<(*it)->GetName()<<endl; } } private: vector<Component *> m_vecComp; }; /** * @brief Principal function * @param None * @retval None */ int main(int argc, char* argv[]) { Component *pNode = new Composite("Beijing Head Office"); Component *pNodeHr = new Leaf("Beijing Human Resources Department"); Component *pSubNodeSh = new Composite("Shanghai Branch"); Component *pSubNodeCd = new Composite("Chengdu Branch"); Component *pSubNodeBt = new Composite("Baotou Branch"); pNode->Add(pNodeHr); pNode->Add(pSubNodeSh); pNode->Add(pSubNodeCd); pNode->Add(pSubNodeBt); pNode->Print(); cout << "------------------------------------" << endl; Component *pSubNodeShHr = new Leaf("Shanghai Human Resources Department"); Component *pSubNodeShCg = new Leaf("Shanghai Purchasing Department"); Component *pSubNodeShXs = new Leaf("Shanghai Sales department"); Component *pSubNodeShZb = new Leaf("Shanghai Quality supervision Department"); pSubNodeSh->Add(pSubNodeShHr); pSubNodeSh->Add(pSubNodeShCg); pSubNodeSh->Add(pSubNodeShXs); pSubNodeSh->Add(pSubNodeShZb); pSubNodeSh->Print(); cout << "------------------------------------" << endl; // The company is in a recession and needs to close the Shanghai Quality Supervision Department pSubNodeSh->Remove(pSubNodeShZb); pSubNodeSh->Print(); cout << "------------------------------------" << endl; if (pNode != NULL) { delete pNode; pNode = NULL; } return 0; }
The results are as follows:
Implementing Key Points
1. One of the keys to Composite is that it is an abstract class that can represent both Leaf and Composite; therefore, in practice, the Component interface should be maximized and the Component class should define as many common operations as possible for Leaf and Composite classes.Component classes usually provide default implementations for these operations, and Leaf and Composite subclasses can redefine them;
2. Should Component implement a Component List, in the code above, I am a list maintained in Composite. Since there is no possibility of sub-Composite in Leaf, I maintain a Component List in Composite, which reduces memory waste;
3. Release of memory; because of the tree structure, when the parent node is destroyed, all the child nodes must also be destroyed, so I do a unified destruction of the maintained Component list in the destructor so that the client can avoid the problem of frequently destroying the child nodes;
4. Because the Component interface provides a maximized interface definition, some operations are not applicable to Leaf nodes, such as: Leaf nodes cannot perform Add and Remove operations, because Composite mode masks some differences from the whole, in order to prevent customers from illegal Add and Remove operations on Leaf, in the actual development process, Add and Remove operations are performed.When doing this, you need to make a corresponding judgment to determine whether the current node is a Composite.
Implementation code for secure combinatorial mode
/**Includes*********************************************************************/ #include <iostream> #include <vector> /**namespace********************************************************************/ using namespace std; // An abstract part class describes the behavior that all parts will share in the future class Component { public: Component(string name) : m_strCompname(name){} virtual ~Component(){} virtual void Operation() = 0; virtual string GetName() { return m_strCompname; } protected: string m_strCompname; }; class Leaf : public Component { public: Leaf(string name) : Component(name) {} void Operation() { cout<<"I'm "<< m_strCompname <<endl; } }; class Composite : public Component { public: Composite(string name) : Component(name) {} ~Composite() { vector<Component *>::iterator it = m_vecComp.begin(); while (it != m_vecComp.end()) { if (*it != NULL) { cout<<"----delete "<<(*it)->GetName()<<"----"<<endl; delete *it; *it = NULL; } m_vecComp.erase(it); it = m_vecComp.begin(); } } void Operation() { cout<<"I'm "<<m_strCompname<<endl; } void Add(Component *pComponent) { m_vecComp.push_back(pComponent); } void Remove(Component *pComponent) { for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it) { if ((*it)->GetName() == pComponent->GetName()) { if (*it != NULL) { delete *it; *it = NULL; } m_vecComp.erase(it); break; } } } Component *GetChild(int index) { if (index > m_vecComp.size()) { return NULL; } return m_vecComp[index - 1]; } void Print() { for (vector<Component *>::iterator it = m_vecComp.begin(); it != m_vecComp.end(); ++it) { cout<<(*it)->GetName()<<endl; } } private: vector<Component *> m_vecComp; }; /** * @brief Principal function * @param None * @retval None */ int main(int argc, char* argv[]) { Composite *pNode = new Composite("Beijing Head Office"); Leaf *pNodeHr = new Leaf("Beijing Human Resources Department"); Composite *pSubNodeSh = new Composite("Shanghai Branch"); Composite *pSubNodeCd = new Composite("Chengdu Branch"); Composite *pSubNodeBt = new Composite("Baotou Branch"); pNode->Add(pNodeHr); pNode->Add(pSubNodeSh); pNode->Add(pSubNodeCd); pNode->Add(pSubNodeBt); pNode->Print(); cout << "------------------------------------" << endl; Leaf *pSubNodeShHr = new Leaf("Shanghai Human Resources Department"); Leaf *pSubNodeShCg = new Leaf("Shanghai Purchasing Department"); Leaf *pSubNodeShXs = new Leaf("Shanghai Sales department"); Leaf *pSubNodeShZb = new Leaf("Shanghai Quality supervision Department"); pSubNodeSh->Add(pSubNodeShHr); pSubNodeSh->Add(pSubNodeShCg); pSubNodeSh->Add(pSubNodeShXs); pSubNodeSh->Add(pSubNodeShZb); pSubNodeSh->Print(); cout << "------------------------------------" << endl; // The company is in a recession and needs to close the Shanghai Quality Supervision Department pSubNodeSh->Remove(pSubNodeShZb); pSubNodeSh->Print(); cout << "------------------------------------" << endl; if (pNode != NULL) { delete pNode; pNode = NULL; } return 0; }
The result is the same as above.
Scenarios for 3.4 mode
The structure and characteristics of the combination mode are analyzed in the previous section, and the following scenarios are applied to it.
1. When it is necessary to represent the hierarchy of an object's whole and part.
2. When it is required to hide a composite object from the user, unlike a single object, the user can use a uniform interface to use all objects in the composite structure.
When you find that the requirements reflect a partial and overall hierarchy, and you want users to be able to ignore the differences between the combined objects and the individual objects and use all the objects in the combined structure in a unified way, you should consider the combined mode.
Extension of 3.5 mode
If you abstract the leaf and branch nodes in the combination mode described earlier, that is, the leaf and branch nodes have children, then the combination mode expands to a complex combination mode, such as the simple component JTextComponent in Java AWT/Swing has subclasses JTextField, JTextArea, and the container component Container has subclasses Window, Panel.The structure diagram of the complex combination pattern is shown in the figure.
3.6 Summary
Through the simple explanations above, we know that the intent of the combination mode is to organize complex objects through the relationship between the whole and the part, to shield the details inside the objects, and to show a unified way to operate the objects, which is a means and a way to deal with more complex objects.Now, in conjunction with the code above, think about how the corporate OA system proposed at the beginning of the article is designed.