一、概述
我们往往总是希望用一致的方式访问不同类型的对象,不论这个对象是同一类系中类型A的对象,还是类型B的对象,OO的多态性为我们提供了这种支持。
Composite模式将这种观点更进一步,当一个复杂对象由多个同一类系中的对象组成的时候,我们仍然希望用与访问单个对象一致的方式来访问该复杂对象(这其实仍是多态性在发挥作用,但在这个多态方法的内部处理使得我们可以做到“用一致的方法访问”这一点,见示例)。
Composite(组合)模式将对象组合成树形结构以表示“部分 -整体”的层次结构,它使得客户对单个对象和复合对象的使用具有一致性。
二、结构
Composite模式的结构如下图所示:
图 1:Composite模式类图示例
上述类图中的Leaf相当于数据结构Tree的叶子节点,而Composite相当于Tree的子节点。实际应用中,是否与上述类图一样,在基类Component中提供Add /Remove /GetChild等方法应视需求而定,因为有些情况下这些方法对于Leaf而言是没有意义的。
三、应用
以下情况使用Composite模式:
1、你想表示对象的部分 -整体层次结构(这是基本的Composite的应用)。
2、你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象,在这些对象上执行某个操作(这才是Composite模式带给我们的好处)。
四、优缺点
Composite模式具有以下优缺点:
1、定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
2、简化客户代码 客户可以一致地使用组合结构和单个对象。通常用户不知道 (也不关心 )处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
3、使得更容易增加新类型的组件 新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。
4、使你的设计变得更加一般化 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
五、举例
Java的AWT中的Component -Container体系是一个很好的Composite模式的例子。Container从Component派生,而Container中又可以包含有多个Component(甚至是Container,因为Container也是Component)。
但是,需要注意的是,是否能通过类似add的操作来添加被包容的对象,形成树状结构不是Composite模式的重点,Composite模式的重点在于,形成特定结构后,是否可以保证用统一的方法,在无需关心各被包容对象的前提下访问该对象,执行某个操作。
因此,虽然可以用Java的Collection构建多种容器类型的树状结构,这是一种Composite,但不是这里所讨论的Composite模式。虽然大家有相同的上层接口Collection,但是,各容器类缺少共同的“某个操作”。对于上面讲的AWT中的Component类系而言,“某个操作”可能是invalidate操作,或者是repaint操作。
在现代OS的文件系统实现中,往往不区分文件 /目录,甚至设备,因为对于系统而言,他们没有太多的不同,在这里,目录就相当于类图中的Composite。下面是一个虚拟的目录管理的例子(真正的目录管理比这可复杂多了,具体可参考 <Unix环境高级编程 >):
#include <iostream>
#include <string>
#include <vector>
using namespace std ;
class AbsFile {
public :
virtual void ls () = 0 ;
virtual ~AbsFile () { } // nothing to do in this demo.
protected :
string m_strName ;
static int m_indent ;
};
int AbsFile ::m_indent = 0 ;
class File : public AbsFile {
public :
File ( const char * name )
{
m_strName = name ;
}
void ls ()
{
for ( int i = 0 ; i < m_indent ; i ++)
cout << ' ' ;
cout << m_strName .c_str () << endl ;
}
};
class Dir : public AbsFile {
public :
Dir ( const char * name )
{
m_strName = name ;
}
void add ( AbsFile * f )
{
m_vFiles .push_back (f );
}
void remove (); // not implemented in this demo.
void ls () {
for ( int i = 0 ; i < m_indent ; i ++)
cout << ' ' ;
cout << m_strName << ":" << endl ;
m_indent += 3 ;
vector <AbsFile *>::iterator it = m_vFiles .begin ();
for (; it != m_vFiles .end (); it ++)
(*it )->ls ();
m_indent -= 3 ;
}
private :
vector <AbsFile *> m_vFiles ;
};
void main ( void )
{
Dir one ( "1" ), two ( "2" ), thr ( "3" );
File a ( "a" ), b ( "b" ), c ( "c" ), d ( "d" ), e ( "e" );
one .add ( &a );
one .add ( &two );
one .add ( &b );
two .add ( &c );
two .add ( &d );
two .add ( &thr );
thr .add ( &e );
one .ls ();
}
上述程序输出如下所示的树状文件结构:
1 :
a
2 :
c
d
3 :
e
b
需要注意的是,上面的示例中采用的File(即类图中的Leaf)没有实现add操作,这并不是所有应用必须遵循的原则,视实际情况的需要,我们可以决定是否需要给File提供add等操作的实现。
参考:
1、http : //home.earthlink.net/~huston2/dp/CompositeDemos.txt
我们往往总是希望用一致的方式访问不同类型的对象,不论这个对象是同一类系中类型A的对象,还是类型B的对象,OO的多态性为我们提供了这种支持。
Composite模式将这种观点更进一步,当一个复杂对象由多个同一类系中的对象组成的时候,我们仍然希望用与访问单个对象一致的方式来访问该复杂对象(这其实仍是多态性在发挥作用,但在这个多态方法的内部处理使得我们可以做到“用一致的方法访问”这一点,见示例)。
Composite(组合)模式将对象组合成树形结构以表示“部分 -整体”的层次结构,它使得客户对单个对象和复合对象的使用具有一致性。
二、结构
Composite模式的结构如下图所示:
![](/images/blog_csdn_net/billdavid/136320/o_Composite.jpg)
图 1:Composite模式类图示例
上述类图中的Leaf相当于数据结构Tree的叶子节点,而Composite相当于Tree的子节点。实际应用中,是否与上述类图一样,在基类Component中提供Add /Remove /GetChild等方法应视需求而定,因为有些情况下这些方法对于Leaf而言是没有意义的。
三、应用
以下情况使用Composite模式:
1、你想表示对象的部分 -整体层次结构(这是基本的Composite的应用)。
2、你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象,在这些对象上执行某个操作(这才是Composite模式带给我们的好处)。
四、优缺点
Composite模式具有以下优缺点:
1、定义了包含基本对象和组合对象的类层次结构 基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
2、简化客户代码 客户可以一致地使用组合结构和单个对象。通常用户不知道 (也不关心 )处理的是一个叶节点还是一个组合组件。这就简化了客户代码,因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
3、使得更容易增加新类型的组件 新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。
4、使你的设计变得更加一般化 容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。
五、举例
Java的AWT中的Component -Container体系是一个很好的Composite模式的例子。Container从Component派生,而Container中又可以包含有多个Component(甚至是Container,因为Container也是Component)。
但是,需要注意的是,是否能通过类似add的操作来添加被包容的对象,形成树状结构不是Composite模式的重点,Composite模式的重点在于,形成特定结构后,是否可以保证用统一的方法,在无需关心各被包容对象的前提下访问该对象,执行某个操作。
因此,虽然可以用Java的Collection构建多种容器类型的树状结构,这是一种Composite,但不是这里所讨论的Composite模式。虽然大家有相同的上层接口Collection,但是,各容器类缺少共同的“某个操作”。对于上面讲的AWT中的Component类系而言,“某个操作”可能是invalidate操作,或者是repaint操作。
在现代OS的文件系统实现中,往往不区分文件 /目录,甚至设备,因为对于系统而言,他们没有太多的不同,在这里,目录就相当于类图中的Composite。下面是一个虚拟的目录管理的例子(真正的目录管理比这可复杂多了,具体可参考 <Unix环境高级编程 >):
#include <iostream>
#include <string>
#include <vector>
using namespace std ;
class AbsFile {
public :
virtual void ls () = 0 ;
virtual ~AbsFile () { } // nothing to do in this demo.
protected :
string m_strName ;
static int m_indent ;
};
int AbsFile ::m_indent = 0 ;
class File : public AbsFile {
public :
File ( const char * name )
{
m_strName = name ;
}
void ls ()
{
for ( int i = 0 ; i < m_indent ; i ++)
cout << ' ' ;
cout << m_strName .c_str () << endl ;
}
};
class Dir : public AbsFile {
public :
Dir ( const char * name )
{
m_strName = name ;
}
void add ( AbsFile * f )
{
m_vFiles .push_back (f );
}
void remove (); // not implemented in this demo.
void ls () {
for ( int i = 0 ; i < m_indent ; i ++)
cout << ' ' ;
cout << m_strName << ":" << endl ;
m_indent += 3 ;
vector <AbsFile *>::iterator it = m_vFiles .begin ();
for (; it != m_vFiles .end (); it ++)
(*it )->ls ();
m_indent -= 3 ;
}
private :
vector <AbsFile *> m_vFiles ;
};
void main ( void )
{
Dir one ( "1" ), two ( "2" ), thr ( "3" );
File a ( "a" ), b ( "b" ), c ( "c" ), d ( "d" ), e ( "e" );
one .add ( &a );
one .add ( &two );
one .add ( &b );
two .add ( &c );
two .add ( &d );
two .add ( &thr );
thr .add ( &e );
one .ls ();
}
上述程序输出如下所示的树状文件结构:
1 :
a
2 :
c
d
3 :
e
b
需要注意的是,上面的示例中采用的File(即类图中的Leaf)没有实现add操作,这并不是所有应用必须遵循的原则,视实际情况的需要,我们可以决定是否需要给File提供add等操作的实现。
参考:
1、http : //home.earthlink.net/~huston2/dp/CompositeDemos.txt