《设计模式》第三部分 结构型设计模式 第11章 组成模式(组合模式)(A:C++实现)

122 阅读6分钟

3.1模式动机

在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣月艮与衣柜以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。

举个具体的例子,下图,是一个公司的组织结构图,总部下面有多个子公司,同时总部也有各个部门,子公司下面有多个部门。如果对这样的公司开发一个OA系统,作为程序员的你,如何设计这个OA系统呢?先不说如何设计实现,接着往下看,看完了下面的内容,再回过头来想怎么设计这样的OA系统。

在这里插入图片描述

图1

3.2模式的定义与特点

组合(Composite)模式的定义:有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。基本对象可以被组合成更大的对象,而且这种操作是可重复的,不断重复下去就可以得到一个非常大的组合对象,但这些组合对象与基本对象拥有相同的接口,因而组合是透明的,用法完全一致。

组合模式的主要优点有:
1.组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
2.更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

主要缺点是:
1.设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2.不容易限制容器中的构件;
3.不容易用继承的方法来增加构件的新功能;

3.3模式的结构与实现

3.3.1模式的结构

组合模式包含以下主要角色。
1.抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。

2.树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。
3.树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式和安全式的组合模式。
(1) 透明方式:在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构图如图所示。

在这里插入图片描述

图2透明式的组合模式的结构图

(2) 安全方式:在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构图如图 2 所示。

在这里插入图片描述

图3安全式的组合模式的结构图

3.3.1模式的实现

 透明式的组合模式的实现代码

/**Includes*********************************************************************/
#include <iostream>
#include <vector>

/**namespace********************************************************************/
using namespace std;

// 抽象的部件类描述将来所有部件共有的行为
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     主函数
  * @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;
    // 公司不景气,需要关闭上海质量监督部门
    pSubNodeSh->Remove(pSubNodeShZb);
    pSubNodeSh->Print();
    cout << "------------------------------------" << endl;
    if (pNode != NULL)
    {
        delete pNode;
        pNode = NULL;
    }

    return 0;
}

结果如下所示:
在这里插入图片描述

【注】实现要点
1.Composite的关键之一在于一个抽象类,它既可以代表Leaf,又可以代表Composite;所以在实际实现时,应该最大化Component接口,Component类应为Leaf和Composite类尽可能多定义一些公共操作。Component类通常为这些操作提供缺省的实现,而Leaf和Composite子类可以对它们进行重定义;
2.Component是否应该实现一个Component列表,在上面的代码中,我是在Composite中维护的列表,由于在Leaf中,不可能存在子Composite,所以在Composite中维护了一个Component列表,这样就减少了内存的浪费;
3.内存的释放;由于存在树形结构,当父节点都被销毁时,所有的子节点也必须被销毁,所以,我是在析构函数中对维护的Component列表进行统一销毁,这样就可以免去客户端频繁销毁子节点的困扰;
4.由于在Component接口提供了最大化的接口定义,导致一些操作对于Leaf节点来说并不适用,比如:Leaf节点并不能进行Add和Remove操作,由于Composite模式屏蔽了部分与整体的区别,为了防止客户对Leaf进行非法的Add和Remove操作,所以,在实际开发过程中,进行Add和Remove操作时,需要进行对应的判断,判断当前节点是否为Composite。

 安全式的组合模式的实现代码

/**Includes*********************************************************************/
#include <iostream>
#include <vector>

/**namespace********************************************************************/
using namespace std;

// 抽象的部件类描述将来所有部件共有的行为
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     主函数
  * @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;
    // 公司不景气,需要关闭上海质量监督部门
    pSubNodeSh->Remove(pSubNodeShZb);
    pSubNodeSh->Print();
    cout << "------------------------------------" << endl;
    if (pNode != NULL)
    {
        delete pNode;
        pNode = NULL;
    }

    return 0;
}

结果同上。

3.4模式的应用场景

前面分析了组合模式的结构与特点,下面分析它适用的以下应用场景。
1.在需要表示一个对象整体与部分的层次结构的场合。
2.要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
当发现需求中是体现部分与整体层次结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑组合模式了。

3.5模式的扩展

如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了,如 Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。复杂的组合模式的结构图如图所示。

在这里插入图片描述

图4复杂的组合模式的结构图

3.6总结

通过上面的简单讲解,我们知道了,组合模式意图是通过整体与局部之间的关系,通过树形结构的形式进行组织复杂对象,屏蔽对象内部的细节,对外展现统一的方式来操作对象,是我们处理更复杂对象的一个手段和方式。现在再结合上面的代码,想想文章开头提出的公司OA系统如何进行设计。