一 简介
什么是组合模式?
将对象组合成树形结构以表示“部分-整体”的层次结构。组合(Composite)模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式(Composite)将小对象组合成树形结构,使用户操作组合对象如同操作一个单个对象。组合模式定义了“部分-整体”的层次结构,基本对象可以被组合成更大的对象,而且这种操作是可重复的,不断重复下去就可以得到一个非常大的组合对象,但这些组合对象与基本对象拥有相同的接口,因而组合是透明的,用法完全一致。
组合模式就是把一些现有的对象或者元素,经过组合后组成新的对象,新的对象提供内部方法,可以让我们很方便的完成这些元素或者内部对象的访问和操作。我们也可以把组合对象理解成一个容器,容器提供各种访问其内部对象或者元素的API,我们只需要使用这些方法就可以操作它了。
二. UML类图
- Component(抽象构件):可以是接口或抽象类,定义了构件的一些公共接口,这些接口是管理或者访问它的子构件的方法(如果有子构件),具体的实现在叶子构件和容器构件中进行。
- Leaf(叶子构件):它代表树形结构中的叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法(非叶子构件功能,属于容器构件的功能),可以通过抛出异常、提示错误等方式进行处理。
- Composite(容器构件)
:表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为。
三 例子
比如目录和文件,目录可以只包含子目录,也可以只包含文件,当然可以同时包含子目录和文件。而子目录又可以包含子目录或者文件目录和文件都可以删除或者打开。打开目录其实就是打开目录的全部子目录和该目录下的文件。删除文件就是删除一个文件,删除目录是删除全部子目录和全部文件。
//相当于Component
class Abstract
{
public:
Abstract(string name) :m_Name(name) {}
virtual ~Abstract() {}
virtual void show() = 0;
virtual void deleteFile(Abstract * abs) = 0;
virtual void addFile(Abstract * abs) = 0;
protected:
string m_Name;
};
//相当于Leaf
class File :public Abstract
{
public:
File(string name) :Abstract(name) {}
~File() { cout << "文件:" << m_Name << "已销毁" << endl; }
void show() { cout << "\t文件:" << m_Name << endl; }
void deleteFile(Abstract * abs) {}//下面的方法, 他显然没有,不用实现
void addFile(Abstract * abs) {}
};
//相当于Composite
class Dir :public Abstract
{
public:
Dir(string name) :Abstract(name) { m_dir.clear(); }
virtual ~Dir()//删除所有文件
{
for (auto it = m_dir.begin(); it != m_dir.end(); it++)
{
delete *it;
}
cout << "目录:" << m_Name <<"已销毁"<< endl;
}
void show()
{
cout << "目录:" << m_Name << endl;
for (auto it = m_dir.begin(); it != m_dir.end(); it++)
(*it)->show();
}
void deleteFile(Abstract * abs)
{
if (m_dir.size() == 0)
{
cout << "该目录为空" << endl;
return;
}
for (auto it = m_dir.begin(); it != m_dir.end(); it++)
{
if (*it == abs)
{
delete *it;
*it = NULL;
m_dir.erase(it);
cout << "删除成功" << endl;
return;
}
(*it)->deleteFile(abs);
}
}
void addFile(Abstract * abs)
{
m_dir.push_back(abs);
}
private:
std::vector<Abstract* >m_dir;
};
int main()
{
{
//添加目录
Abstract *dir = new Dir("总目录");
Abstract *film = new Dir("电影");
Abstract *game = new Dir("游戏");
dir->addFile(film);
dir->addFile(game);
//添加电影目录
Abstract * TANG1 = new File("唐探1");
Abstract * TANG2 = new File("唐探2");
Abstract * TANG3 = new File("唐探3");
film->addFile(TANG1);
film->addFile(TANG2);
film->addFile(TANG3);
//添加游戏目录
Abstract * game1 = new File("LOL");
Abstract * game2 = new File("DNF");
Abstract * game3 = new File("CF");
game->addFile(game1);
game->addFile(game2);
game->addFile(game3);
dir->show();
dir->deleteFile(game);
dir->show();
dir->deleteFile(TANG3);
delete dir;
}
getchar();
return 0;
}
结果
四优缺点
优点:
- 能清楚地定义一个复杂系统的层次结构,方便对层次结构进行管理。
- 可以增加新的容器对象与叶子对象,符合开闭原则。
- 为树形结构对象提供了灵活的解决方案,通过叶子对象与容器对象递归组合可以形成复杂的树形结构,但对树形结构的控制却非常简单。
缺点:
- 在增加新的类型时很难对容器中的构件类型进行限制,他们拥有相同的抽象层,所以对某个容器类型去限制只具有某个指定的叶子对象或者容器对象是很复杂的。即,目录下两个文件夹,一个存文件,一个存目录,需要指针动态转换去区分,而动态转换对效率影响是非常大的。
五 组合模式的两种形式
此例是透明方式
透明方式:Component里面申明所有用来管理子对象的方法
优点:所有构建类都有相同的接口,组合对象与单个对象的区别在接口层次上消失了,客户端可以同等的对待所有的对象;
缺点:不安全,树叶类对象没有下一个层次的对象,这样的话有一些方法(add(),remove())等操作没有意义,这种编译是不会报错,但运行时会出错。
安全方式:在Componsite类里声明所有的用来管理子类对象的方法。
优点:这样做是安全的,因为树叶类型的对象没有管理子类对象的方法,如果客户端对树叶类对象使用这些方法时,编译时会出错,这样程序就到不了运行阶段。
缺点:不够透明,树叶类和合成类具有不同的接口。