1. 概述
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
2. 解决的问题
当希望忽略单个对象和组合对象的区别,统一使用组合结构中的所有对象(将这种“统一”性封装起来)。
3. 组合模式中的角色
3.1 组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
3.2 叶子(Leaf):在组合中表示子节点对象,叶子节点不能有子节点。
3.3 合成部件(Composite):定义有枝节点的行为,用来存储部件,实现在Component接口中的有关操作,如增加(Add)和删除(Remove)。
4. 组合模式中两个相互关联的重要概念
1)一个组合对象可以包含单对象或者其他组合对象;
2)组合对象与单对象共享同一个接口。
5. 在对组合对象建模时,需要注意防止死循环
1)在使用递归定义后,要确保组合对象都是树形结构;(在遍历对象模型后,任何节点至多被访问一次,则该模型是一颗树)
2)当环出现在组合模式中时,必须修改算法避免可能出现的死循环。
6. 模式解读
6.1 组合模式的类图
6.2 组合模式举例
/************************************************************************/
/* 组合模式 */
/************************************************************************/
/*
假如我们先公司现在的结构如下,
结构图:
-北京市总公司
---总公司人力资源部
---总公司财务部
---青岛市分公司
-----青岛市分公司人力资源部
-----青岛市分公司财务部
-----城阳区办事处
-------城阳区办事处人力资源部
-------城阳区办事处财务部
-----崂山区办事处
-------崂山区办事处人力资源部
-------崂山区办事处财务部
*/
#include <IOSTREAM>
#include <LIST>
using namespace std;
/*
公司类抽象
*/
class Company
{
protected:
char *name;
public:
Company(char *name)
{
this->name = name;
}
virtual void Add(Company *c) {}//增加
virtual void Remove(Company *c) {}//移除
virtual void Display(int depth) {}//显示
virtual void LineOfDuty() {}//履行职责
};
//具体的公司类(树枝)
class ConcreteCompany : public Company
{
private:
list<Company*> *child;
public:
ConcreteCompany(char *name):Company(name)
{
child = new list<Company*>;
}
void Add(Company *c)
{
child->push_back(c);
}
void Remove(Company *c)
{
child->pop_back();
}
void Display(int depth)
{
int temp = depth;
list<Company*>::iterator it = child->begin();
while(temp--)
cout<<"-";
cout<<name<<endl;
//显示所有的分支和叶子的名称
for(;it!=child->end();it++){
(*it)->Display(depth+2);
}
}
//履行职责
void LineOfDuty()
{
list<Company*>::iterator it = child->begin();
//显示所有的分支和叶子的名称
for(;it!=child->end();it++){
//执行分支或叶子的职责
(*it)->LineOfDuty();
}
}
};
//人力资源部(叶子),叶子中的添加删除函数都是空实现
class HRDepartment : public Company
{
public:
HRDepartment(char *name):Company(name)
{
}
public:
void Add(Company c)
{
}
void Remove(Company c)
{
}
void Display(int depth)
{
int temp = depth;
while(temp--)
cout<<"-";
cout<<name<<endl;
}
void LineOfDuty()
{
cout<<name<<" 员工招聘培训管理"<<endl;
}
};
//财务部
class FinanceDeparment : public Company
{
public:
FinanceDeparment(char *name):Company(name)
{
}
void Add(Company *c)
{
}
void Remove(Company *c)
{
}
void Display(int depth)
{
int temp = depth;
while(temp--)
cout<<"-";
cout<<name<<endl;
}
void LineOfDuty()
{
cout<<name<<" 公司财务收支管理"<<endl;
}
};
//客户端调用
void main()
{
ConcreteCompany *root = new ConcreteCompany("北京市总公司");
root->Add(new HRDepartment("总公司人力资源部"));
root->Add(new FinanceDeparment("总公司财务部"));
ConcreteCompany *comp = new ConcreteCompany("青岛市分公司");
comp->Add(new HRDepartment("青岛市分公司人力资源部"));
comp->Add(new FinanceDeparment("青岛市分公司财务部"));
root->Add(comp);
ConcreteCompany *comp1 = new ConcreteCompany("城阳区办事处");
comp1->Add(new HRDepartment("城阳区办事处人力资源部"));
comp1->Add(new FinanceDeparment("城阳区办事处财务部"));
comp->Add(comp1);//城阳区办事处隶属于青岛市分公司
ConcreteCompany *comp2 = new ConcreteCompany("崂山区办事处");
comp2->Add(new HRDepartment("崂山区办事处人力资源部"));
comp2->Add(new FinanceDeparment("崂山区办事处财务部"));
comp->Add(comp2);//城阳区办事处隶属于青岛市分公司
cout<<"结构图:"<<endl;
root->Display(1);
cout<<endl;
cout<<endl;
cout<<"职责是:"<<endl;
root->LineOfDuty();
}
/*
实例应用,如果这家公司让我们为他的不同等级的部门和各公司分别做一套软件
而且(假如)上级需要包含下级所具备的功能,那么我们就可以使用组合模式,使用这种模式就大大简化了工作
*/
运行结果
7. 透明方式与安全方式
7.1 透明方式:在Component中声明所有来管理子对象的方法,其中包括Add,Remove等。这样实现Component接口的所有子类都具备了Add和Remove方法。这样做的好处是叶节点和枝节点对于外界没有区别,它们具备完全一致的接口。
7.2 安全方式:在Component中不去声明Add和Remove方法,那么子类的Leaf就不需要实现它,而是在Composit声明所有用来管理子类对象的方法。
7.3 两种方式有缺点:对于透明方式,客户端对叶节点和枝节点是一致的,但叶节点并不具备Add和Remove的功能,因而对它们的实现是没有意义的;对于安全方式,叶节点无需在实现Add与Remove这样的方法,但是对于客户端来说,必须对叶节点和枝节点进行判定,为客户端的使用带来不便。
8. 模式总结
8.1 优点
8.1.1 使客户端调用简单,它可以一致使用组合结构或是其中单个对象,简化了客户端代码。
81.2 容易在组合体内增加对象部件。客户端不必因加入了新的部件而更改代码。有利于功能的扩展。
8.2 缺点
8.2.1 需要抉择使用透明方式还是安全方式。
8.2.2 透明方式违背了面向对象的单一职责原则;安全方式增加了客户需要端判定的负担。
8.3 适用场景
8.3.1 当想表达对象的部分-整体的层次结构时
8.3.3 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。