组合模式(结构型)

前言


在稍微大一些的公司组织结构体系中,会有不同的功能部门,如HR Department、Financial Department等等。而有时候为了方便异地管理,又设置不同的子公司,子公司内部又有上述不同的功能部门组成,对于这种类似于文件系统的树形结构,可以考虑使用组合模式去实现。

组合模式


组合模式,该模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和组合节点(包含子节点的节点)。

意图

将对象组合成树形结构以表示“部分、整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

参与者

  • Component
    为组合中的对象声明接口;
    在适当的情况下,实现所有类共有接口的缺省行为;
    声明的一个接口用于访问和管理Component的子组件;
    (可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它;

  • Leaf
    在组合中表示叶节点对象(叶节点没有子节点);
    在组合中定义对象的行为;

  • Composite
    定义有子部件的那些部件的行为;
    存储子部件;
    在Component接口中实现与子部件有关的操作

  • Client
    通过Component接口操纵组合部件的对象

模式结构

component

代码实现

  1. 首先定义一个实现类接口AbstractComponent,及操作接口Operator()和访问部件的接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 抽象的组件类
    class AbstractComponent
    {
    public:
    virtual ~AbstractComponent() {};
    public:
    virtual void Operator() = 0;
    virtual void Add(shared_ptr<AbstractComponent> &pAC) = 0;
    virtual void Remove(shared_ptr<AbstractComponent> &pAC) = 0;
    virtual shared_ptr<AbstractComponent>& GetChild(int n) = 0;
    virtual void Show(int nDepth) = 0;
    };
  2. 定义叶子组件类Leaf继承与AbstractComponent,并实现其相应接口Operator(),对于Add()Remove()GetChild(),实现为空或进行异常处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Leaf : public AbstractComponent
    {
    private:
    string m_strName;
    public:
    Leaf(string strName) : m_strName(strName) {};
    virtual ~Leaf() {};
    public:
    void Operator()
    {
    cout << " I am Named" << m_strName << "Leaf Operator" << endl;
    }
    // Leaf组件对象内部没有子对象,故Add,Removew为空,必须去实现AbstractComponent的Virtual接口,即使为空
    void Add(shared_ptr<AbstractComponent> &pAC) {};
    void Remove(shared_ptr<AbstractComponent> &pAC) {};
    shared_ptr<AbstractComponent>& GetChild(int n) {return shared_ptr<AbstractComponent>(0);}
    void Show(int nDepth)
    {
    for(int i = 0; i < nDepth; i++)
    {
    cout << "--" << flush; // 清除缓存区 不换行,endl 是清除缓冲区 + 换行
    }
    cout << m_strName << endl;
    };
    };
  3. 再定义一个组合组件类Composite继承与AbstractComponent,并实现其相应接口Operator()Add()Remove()GetChild()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // 组合组件类,里面包含其他组件对象
    class Composite : public AbstractComponent
    {
    private:
    string m_strName;
    vector<shared_ptr<AbstractComponent>> m_VecComponent;
    public:
    Composite(string strName): m_strName(strName) {};
    virtual ~Composite() {};
    public:
    void Operator()
    {
    cout << " I am Named" << m_strName << "Composite Operator" << endl;
    }
    void Add(shared_ptr<AbstractComponent> &pAC)
    {
    m_VecComponent.push_back(pAC);
    }
    void Remove(shared_ptr<AbstractComponent> &pAC) // 删除与指定组件对象名字一样的
    {
    for (vector<shared_ptr<AbstractComponent>>::iterator it = m_VecComponent.begin();
    it !=m_VecComponent.end();
    ++it)
    {
    if ((*it) == pAC)
    {
    m_VecComponent.erase(it);
    break;
    }

    }
    }
    shared_ptr<AbstractComponent>& GetChild(int n)
    {
    if (n > m_VecComponent.size())
    {
    cout << "n is out of range" << endl;
    return shared_ptr<AbstractComponent>(0);
    }
    return m_VecComponent[n];
    }

    void Show(int nDepth)
    {
    // 显示当前组合对象自己的名字信息
    for (int i = 0; i< nDepth; i++)
    {
    cout << "--" << flush;
    }
    cout << m_strName << endl;

    // 显示当前组合对象容器内部包含的子对象的名字信息
    for (vector<shared_ptr<AbstractComponent>>::iterator it = m_VecComponent.begin();
    it != m_VecComponent.end();
    ++it)
    {
    (*it)->Show(nDepth + 2);
    }
    }
    };
  4. 测试组合模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    void CompositeTest_General()
    {
    // 创建根节点
    shared_ptr<AbstractComponent>pAC_Root(new Composite("Root"));
    //创建并添加两个根节点的叶子节点A,B
    shared_ptr<AbstractComponent>pAC_Leaf_Root_A(new Leaf("Leaf A"));
    pAC_Root->Add(pAC_Leaf_Root_A);

    shared_ptr<AbstractComponent>pAC_Leaf_Root_B(new Leaf("Leaf B"));
    pAC_Root->Add(pAC_Leaf_Root_B);

    //创建并添加一个根节点的组合节点A,该组合节点内包含两个Leaf节点A,B
    shared_ptr<AbstractComponent>pAC_Sub_Composite(new Composite("Sub Composite A"));
    pAC_Root->Add(pAC_Sub_Composite);

    // 创建并添加该组合节点内部包含的两个Leaf节点
    shared_ptr<AbstractComponent>pAC_Leaf_Sub_A(new Leaf("Sub Composite Leaf A"));
    pAC_Sub_Composite->Add(pAC_Leaf_Sub_A);

    shared_ptr<AbstractComponent>pAC_Leaf_Sub_B(new Leaf("Sub Composite Leaf B"));
    pAC_Sub_Composite->Add(pAC_Leaf_Sub_B);

    //创建并添加根节点的另一个组合节点A2,该组合节点内包含两个Leaf节点A2,B2
    shared_ptr<AbstractComponent>pAC_Sub_Composite2(new Composite("Sub Composite A2"));
    pAC_Root->Add(pAC_Sub_Composite2);

    // 创建并添加该组合节点内部包含的两个Leaf节点
    shared_ptr<AbstractComponent>pAC_Leaf_Sub_A2(new Leaf("Sub Composite Leaf A2"));
    pAC_Sub_Composite2->Add(pAC_Leaf_Sub_A2);

    shared_ptr<AbstractComponent>pAC_Leaf_Sub_B2(new Leaf("Sub Composite Leaf B2"));
    pAC_Sub_Composite2->Add(pAC_Leaf_Sub_B2);

    // 显示Root下所有组合节点和Leaf节点
    cout << "Before Remove pAC_Leaf_Sub_B2:" << endl <<endl;
    pAC_Root->Show(0);

    // 删除组合节点A2
    cout << "After Remove pAC_Leaf_Sub_B2:" << endl << endl;
    pAC_Sub_Composite2->Remove(pAC_Leaf_Sub_B2);
    pAC_Root->Show(0);
    //只需要释放根节点,其他节点在根节点的析构函数内释放掉了
    }

运行结果:

Before Remove pAC_Leaf_Sub_B2:
Root
----Leaf A
----Leaf B
----Sub Composite A
--------Sub Composite Leaf A
--------Sub Composite Leaf B
----Sub Composite A2
--------Sub Composite Leaf A2
--------Sub Composite Leaf B2
After Remove pAC_Leaf_Sub_B2:
Root
----Leaf A
----Leaf B
----Sub Composite A
--------Sub Composite Leaf A
--------Sub Composite Leaf B
----Sub Composite A2
--------Sub Composite Leaf A2

对于叶子节点改进

在增加新的组合类型时,无须修改现有类库代码,只需增加一个新的组合类作为AbstractComponent类的子类即可,但是由于在AbstractComponent中声明了大量用于管理和访问成员构件的方法,例如add()remove()等方法,我们不得不在新增的类中实现这些方法,或者提供对应的错误提示和异常处理。为了简化代码,可以考虑下面两种解决方案:

透明组合模式

将叶子构件的add()remove()等方法的实现代码移至AbstractComponent父类中,由父类提供一个默认实现。

透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。叶子对象不可能有下一个层次的对象,因此为其提供add()、remove()以及getChild()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

安全组合模式

安全组合模式中,在抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法。这种做法是安全的,因为根本不向叶子对象提供这些管理成员对象的方法,对于叶子对象,客户端不可能调用到这些方法。

安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件

增加一个获取Composite的接口来区分

一种办法是在Component类中声明一个操作Composite* GetComposite()Component提供提供了一个返回空指针的缺省操作。Composite类重新定义这个操作并通过this指针来返回对象本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Composite;

class Component
{
//...
virtual Composite* GetComposite() {return 0;}
};

class Composite: public Component
{
//...
virtual Composite* GetComposite() {return this;}
}

class Leaf : public Component
{
//...
}

使用的时候,通过判断对象类型来进行相应调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Composite *composite = new Composite;
Leaf* leaf = new Leaf;

Component *component;
Composite *test;

component = composite;

if(test = component->GetComposite())
{
test->Add(new Leaf); // willl add leaf
}

component = leaf;

if(test = component->GetComposite())
{
test->Add(new Leaf); // will not add leaf
}

使用场景

  • 想表示对象的部分-整体层次结构
  • 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  • 一个对象有多个变化因素的时候,通过抽象这些变化因素,将依赖具体实现,修改为依赖抽象
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

优缺点

  • 优点
    • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
    • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
    • 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”
    • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单
  • 缺点
    • 在增加新构件时很难对容器中的构件类型进行限制,有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层

组合模式具体实例


公司组织结构

实现前言所描述的公司组织结构

代码实现

  1. 抽象公司类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 抽象公司类
    class Company
    {
    private:
    string m_strName;
    public:
    Company(string strName) : m_strName(strName) {};
    virtual ~Company() {};
    public:
    virtual void Add(Company *pC) = 0;
    virtual void Remove(Company *pC) = 0;
    virtual string GetName()
    {
    return m_strName;
    }
    virtual void Show(int nDepth) = 0;
    };
  2. 公司的两个部门(叶子节点):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    // 叶子节点1---金融部门
    class FinanceDepartment : public Company
    {
    public:
    FinanceDepartment(string strName) : Company(strName) {};
    virtual ~FinanceDepartment() {};
    public:
    void Add(Company *pC) {};
    void Remove(Company *pC) {};
    void Show(int nDepth)
    {
    for (int i = 0; i < nDepth; i++)
    {
    cout << "---" << flush;
    }
    cout << GetName() << endl;
    }
    };
    // 叶子节点2-- HR部门
    class HRDepartment : public Company
    {
    public:
    HRDepartment(string strName) : Company(strName) {};
    virtual ~HRDepartment() {};
    public:
    void Add(Company *pC) {};
    void Remove(Company *pC) {};
    void Show(int nDepth)
    {
    for (int i = 0; i < nDepth; i++)
    {
    cout << "---" << flush;
    }
    cout << GetName() << endl;
    }
    };
  3. 组合公司(组合节点):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    // 组合公司
    class CompositeCompany : public Company
    {
    private:
    list<Company*> m__ListCompany;
    public:
    CompositeCompany(string strName) : Company(strName) {};
    virtual ~CompositeCompany()
    {
    for (list<Company*>::iterator it = m__ListCompany.begin();
    it != m__ListCompany.end();
    ++it)
    {
    if (NULL != (*it))
    {
    delete *it;
    *it = NULL;
    }
    m__ListCompany.erase(it);
    }
    };
    public:
    void Add(Company *pC)
    {
    m__ListCompany.push_back(pC);
    };
    void Remove(Company *pC)
    {
    for (list<Company*>::iterator it = m__ListCompany.begin();
    it != m__ListCompany.end();
    ++it) // ++it 不是it++
    {
    if ((*it)->GetName() == pC->GetName())
    {
    delete *it;
    *it = NULL;
    m__ListCompany.erase(it); // 防止erase之后迭代器失效
    break;
    }

    }
    };
    void Show(int nDepth)
    {
    for (int i = 0; i < nDepth; i++)
    {
    cout << "---" << flush;
    }
    cout << GetName() << endl;
    for (list<Company*>::iterator it = m__ListCompany.begin();
    it != m__ListCompany.end();
    ++it)
    {
    if (NULL != (*it))
    {
    (*it)->Show(nDepth + 2);
    }
    }
    }
    };

  4. 测试Computer桥接模式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    void CompositeTest_Company()
    {
    Company *pC_TopCpy = new CompositeCompany("Top Company");
    Company *pC_Finance = new FinanceDepartment("Company Finance Dept");
    Company *pC_HR = new HRDepartment("Company HR Dept");

    pC_TopCpy->Add(pC_Finance);
    pC_TopCpy->Add(pC_HR);

    Company *pC_SubCpy = new CompositeCompany("Sub Company");
    Company *pC_Sub_HR = new HRDepartment("Sub Company HR Dept");
    pC_SubCpy->Add(pC_Finance);
    pC_SubCpy->Add(pC_HR);
    pC_SubCpy->Add(pC_Sub_HR);

    pC_TopCpy->Add(pC_SubCpy);
    cout << "Before Remove Sub Company HR Dept" << endl;

    pC_TopCpy->Show(0);

    cout << "After Remove Sub Company HR Dept" << endl;
    pC_SubCpy->Remove(pC_Sub_HR);
    pC_TopCpy->Show(0);
    }
  5. 运行结果:

    Before Remove Sub Company HR Dept
    Top Company
    ——Company Finance Dept
    ——Company HR Dept
    ——Sub Company
    ————Company Finance Dept
    ————Company HR Dept
    ————Sub Company HR Dept
    After Remove Sub Company HR Dept
    Top Company
    ——Company Finance Dept
    ——Company HR Dept
    ——Sub Company
    ————Company Finance Dept
    ————Company HR Dept