1. 概述
组合模式(Composite),将对象组合成树形结构以表示“部分——整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。有时候我们又称其为部分——整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素。从而使得客户与复杂元素的内部结构解耦。
在一些递归或分级数据结构中,组合模式非常有用武之地。例如,文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就是以递归结构来组织的。如果你想要描述这样的数据结构,那么你就可以使用组合模式。
2. 结构图
下面是组合模式的结构图:
- Component:为组合中的对象声明接口;在适当的情况下,实现所有类共有接口的缺省行为;声明一个接口用于访问和管理 Component 的子组件。
- Leaf:在组合中表示叶节点对象,叶节点没有子节点。
- Composite:定义有子部件的那些部件的行为;存储子部件;在Component 接口中实现与子部件有关的操作。
- Client:通过Component 接口操纵组合部件的对象。
用户使用Component 类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作。
组合模式中又可分为
透明方式和
安全方式。两种各有好处,使用什么方式视情况而定。
透明方式,也就是说在Component 中声明所有用来管理子对象的方法,其中包括Add、Remove等。这样实现Component 接口的所有子类都具备了Add和Remove。这样做的好处就是叶节点和枝节点对外界没有区别。它们具备完全一致的行为接口。但是问题也很明显。因为Leaf 类本身不具备Add()、Remove()方法功能,所以实现它是没有意义的。
安全方式,也就是在Component 接口中不去声明Add 和 Remove方法,那么子类的Leaf 也就不需要去实现它,而是在Composite 声明所有用来管理子类的方法,这样做就不会出现透明方式存在的问题,不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客户端的调用需要做相应的判断,带来不便。
3. 案例分析
在一些绘图编辑器和图形捕捉系统这样的图形应用程序中,用户可以使用简单的组件创建复杂的图表。用户可以组合多个简单组件以形成一些较大的组件,这些组件又可以组合成更大的组件。一个简单的实现方式时为Text和Line这样的图元定义一些类,另外定义一些类作为这些图元的容器类。然而这种方法存在一个问题:使得这些类的代码必须区别对待图元与容器对象,而实际上大多数情况下用户认为它们时一样的。对这些类区别使用,使得程序更加复杂。Composite模式描述了如何使用递归组合,使得用户不必对这些类进行区别。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Graphic.h
*Author: Huangjh
*Version:
*Date: 2017-11-12
*Description: 相当于结构图中的Component角色——声明组合中的对象接口
*Others:
**********************************************************************************/
#ifndef _GRAPHIC_H
#define _GRAPHIC_H
class CGraphic
{
public:
CGraphic() { }
virtual ~CGraphic() { }
virtual void Draw(void) = 0;
virtual void Add(CGraphic *pGraphic) = 0;
virtual void Remove(CGraphic *pGraphic) = 0;
};
#endif #ifndef _GRAPHIC_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Line.h
*Author: Huangjh
*Version:
*Date: 2017-11-12
*Description: 相当于结构图中的Leaf角色——基本图元线
*Others:
**********************************************************************************/
#ifndef _LINE_H
#define _LINE_H
#include <iostream>
#include <string>
#include "Graphic.h"
class CLine : public CGraphic
{
public:
CLine(std::string strName)
:m_strName(strName)
{
}
virtual ~CLine() { }
void Draw(void)
{
std::cout << "基本图元——绘制" << m_strName << std::endl;
}
void Add(CGraphic *pGraphic)
{
std::cout << "基本图元线——Add方法不使用" << std::endl;
}
void Remove(CGraphic *pGraphic)
{
std::cout << "基本图元线——Remove方法不使用" << std::endl;
}
private:
std::string m_strName;
};
#endif //#ifndef _LINE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Text.h
*Author: Huangjh
*Version:
*Date: 2017-11-12
*Description: 相当于结构图中的Leaf角色——基本图元文本
*Others:
**********************************************************************************/
#ifndef _TEXT_H
#define _TEXT_H
#include <iostream>
#include <string>
#include "Graphic.h"
class CText : public CGraphic
{
public:
CText(std::string strName)
:m_strName(strName)
{
}
virtual ~CText() { }
void Draw(void)
{
std::cout << "基本图元——绘制" << m_strName << std::endl;
}
void Add(CGraphic *pGraphic)
{
std::cout << "基本图元文本——Add方法不使用" << std::endl;
}
void Remove(CGraphic *pGraphic)
{
std::cout << "基本图元文本——Remove方法不使用" << std::endl;
}
private:
std::string m_strName;
};
#endif //#ifndef _TEXT_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Rectangle.h
*Author: Huangjh
*Version:
*Date: 2017-11-12
*Description: 相当于结构图中的Composite角色——组合对象矩形(简单的认为矩形有4条线组成)
*Others:
**********************************************************************************/
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include <iostream>
#include <string>
#include <set>
#include "Graphic.h"
class CRectangle : public CGraphic
{
public:
CRectangle(std::string strName)
:m_strName(strName)
{
}
~CRectangle() { }
void Draw(void)
{
std::cout << "组合对象矩形:" << m_strName << "由下面图元组成:" << std::endl;
for (setGraphic::iterator iter = m_setGraphic.begin();
iter != m_setGraphic.end(); ++iter)
{
(*iter)->Draw();
}
}
void Add(CGraphic *pGraphic)
{
m_setGraphic.insert(pGraphic);
}
void Remove(CGraphic *pGraphic)
{
setGraphic::iterator iter = m_setGraphic.find(pGraphic);
if (iter != m_setGraphic.end())
{
m_setGraphic.erase(iter);
}
}
private:
typedef std::set<CGraphic *> setGraphic;
setGraphic m_setGraphic;
std::string m_strName;
};
#endif //#ifndef _RECTANGLE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Label.h
*Author: Huangjh
*Version:
*Date: 2017-11-12
*Description: 相当于结构图中的Composite角色——组合对象标签( 简单的任务标签由矩形和文本组成)
*Others:
**********************************************************************************/
#ifndef _LABEL_H
#define _LABEL_H
#include <iostream>
#include <string>
#include <set>
#include "Graphic.h"
class CLabel : public CGraphic
{
public:
CLabel(std::string strName)
:m_strName(strName)
{
}
~CLabel() { }
void Draw(void)
{
std::cout << "组合对象标签:" << m_strName <<"由下面图元组成:" << std::endl;
for (setCraphic::iterator iter = m_setLabelGraphic.begin();
iter != m_setLabelGraphic.end(); ++iter)
{
(*iter)->Draw();
}
}
void Add(CGraphic *pGraphic)
{
m_setLabelGraphic.insert(pGraphic);
}
void Remove(CGraphic *pGraphic)
{
setCraphic::iterator iter = m_setLabelGraphic.find(pGraphic);
if (iter != m_setLabelGraphic.end())
{
m_setLabelGraphic.erase(iter);
}
}
private:
typedef std::set<CGraphic *> setCraphic;
setCraphic m_setLabelGraphic;
std::string m_strName;
};
#endif #ifndef _LABEL_H
下面是运行的结果:
组合对象标签:一个标签由下面图元组成:
组合对象矩形:一个矩形由下面图元组成:
基本图元——绘制矩形顶部的直线
基本图元——绘制矩形底部的直线
基本图元——绘制矩形左边的直线
基本图元——绘制矩形右边的直线
基本图元——绘制标签中的文本
4. 优缺点
4.1 优点
- 定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
- 简化客户代码 客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
- 使得更容易增加新类型的组件 新定义的Composite 或Leaf 子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component 类而改变。
- 使你的设计变得更加一般化
4.2 缺点
- 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite 时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
5. 适用性
以下情况使用Composite 模式:
- 你想表示对象的部分——整体层次结构。
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
6. 参考资料
- 《大话设计模式》
- 《设计模式——可复用的面向对象软件的基础》