博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式-Composite Pattern
阅读量:5816 次
发布时间:2019-06-18

本文共 8035 字,大约阅读时间需要 26 分钟。

  hot3.png

一、 合成(Composite)模式

合成模式有时又叫做部分-整体模式(Part-Whole)。合成模式将对象组织到树结构中,可以用来描述整体与部分的关系。合成模式可以使客户端将单纯元素与复合元素同等看待。

从和尚的故事谈起

这是小时候我奶奶讲的故事:从前有个山,山里有个庙,庙里有个老和尚在给小和尚讲故事,讲的什么故事呢?从前有个山,山里有个庙……。奶奶的故事要 循环多少次,根据你多长时间睡着而定。在故事中有山、有庙、有和尚、有故事。因此,故事的角色有两种:一种里面没有其它角色;另一种内部有其它角色。

对象的树结构

一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。

注意:一个树枝节点可以不带任何叶子,但是它因为有带叶子的能力,因此仍然是树枝节点,而不会成为叶节点。一个树叶节点永远不可能带有子节点。

二、 合成模式概述

下图所示的类图省略了各个角色的细节。

 Pic59.gif

可以看出,上面的类图结构涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参与组合的对象规定一个接口。这个角色给出共有接口及其默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象。一个树叶对象没有下级子对象。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,并给出树枝构件对象的行为。

可以看出,Composite类型的对象可以包含其它Component类型的对象。换而言之,Composite类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf)类型的对象。

合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。合成模式可以不提供父对象的管理方法,但合成模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)。

透明方式

作为第一种选择,在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。 这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等同的对待所有的对象。这 就是透明形式的合成模式。

这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错。

安全方式

第二种选择是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。

这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。

这两个形式各有优缺点,需要根据软件的具体情况做出取舍决定。

三、 安全式的合成模式的结构

安全式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件中。

 Pic60.gif

这种形式涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。在安全式的合成模式里,构件角色并不是定义出管理子对象的方法,这一定义由树枝构件对象给出。
  • 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
  • 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,如add()、remove()、getChild()等。

四、 安全式的合成模式实现

以下示例性代码演示了安全式的合成模式代码:

None.gif
//
 Composite pattern -- Structural example  
None.gif
using
 System;
None.gif
using
 System.Text;
None.gif
using
 System.Collections;
None.gif
None.gif
//
 "Component"
None.gif
abstract
 
class
 Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
protected
 
string
 name;
InBlock.gif
InBlock.gif  
//
 Constructors
InBlock.gif
  
public
 Component( 
string
 name )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
this
.name 
=
 name;
ExpandedSubBlockEnd.gif  }
InBlock.gif
InBlock.gif  
//
 Operation
InBlock.gif
  
public
 
abstract
 
void
 Display( 
int
 depth );
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Composite"
None.gif
class
 Composite : Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
private
 ArrayList children 
=
 
new
 ArrayList();
InBlock.gif
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 Composite( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
public
 
void
 Add( Component component )
ExpandedSubBlockStart.gif  
{
InBlock.gif    children.Add( component );
ExpandedSubBlockEnd.gif  }
InBlock.gif  
public
 
void
 Remove( Component component )
ExpandedSubBlockStart.gif  
{
InBlock.gif    children.Remove( component );
ExpandedSubBlockEnd.gif  }
InBlock.gif  
public
 
override
 
void
 Display( 
int
 depth )
ExpandedSubBlockStart.gif  
{
InBlock.gif    Console.WriteLine( 
new
 String( 
'
-
'
, depth ) 
+
 name );
InBlock.gif
InBlock.gif    
//
 Display each of the node's children
InBlock.gif
    
foreach
( Component component 
in
 children )
InBlock.gif      component.Display( depth 
+
 
2
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Leaf"
None.gif
class
 Leaf : Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 Leaf( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
public
 
override
 
void
 Display( 
int
 depth )
ExpandedSubBlockStart.gif  
{
InBlock.gif    Console.WriteLine( 
new
 String( 
'
-
'
, depth ) 
+
 name );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}
None.gif
ExpandedBlockStart.gif
///
 
<summary>
InBlock.gif
///
 Client test
ExpandedBlockEnd.gif
///
 
</summary>
None.gif
public
 
class
 Client
ExpandedBlockStart.gif
{
InBlock.gif  
public
 
static
 
void
 Main( 
string
[] args )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
//
 Create a tree structure
InBlock.gif
    Composite root 
=
 
new
 Composite( 
"
root
"
 );
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf A
"
 ));
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf B
"
 ));
InBlock.gif    Composite comp 
=
 
new
 Composite( 
"
Composite X
"
 );
InBlock.gif
InBlock.gif    comp.Add( 
new
 Leaf( 
"
Leaf XA
"
 ) );
InBlock.gif    comp.Add( 
new
 Leaf( 
"
Leaf XB
"
 ) );
InBlock.gif    root.Add( comp );
InBlock.gif
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf C
"
 ));
InBlock.gif
InBlock.gif    
//
 Add and remove a leaf
InBlock.gif
    Leaf l 
=
 
new
 Leaf( 
"
Leaf D
"
 );
InBlock.gif    root.Add( l );
InBlock.gif    root.Remove( l );
InBlock.gif
InBlock.gif    
//
 Recursively display nodes
InBlock.gif
    root.Display( 
1
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}

五、 透明式的合成模式结构

与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。

 Pic61.gif

这种形式涉及到三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add()、remove()以及getChild()之类的用来管理子类对对象的方法的平庸实现。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的对象的行为。

六、 透明式的合成模式实现

以下示例性代码演示了安全式的合成模式代码:

None.gif
//
 Composite pattern -- Structural example  
None.gif
None.gif
using
 System;
None.gif
using
 System.Text;
None.gif
using
 System.Collections;
None.gif
None.gif
//
 "Component"
None.gif
abstract
 
class
 Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
protected
 
string
 name;
InBlock.gif
InBlock.gif  
//
 Constructors
InBlock.gif
  
public
 Component( 
string
 name )
ExpandedSubBlockStart.gif  
this
.name 
=
 name; }
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
abstract
 
public
 
void
 Add(Component c);
InBlock.gif  
abstract
 
public
 
void
 Remove( Component c );
InBlock.gif  
abstract
 
public
 
void
 Display( 
int
 depth );
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Composite"
None.gif
class
 Composite : Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
private
 ArrayList children 
=
 
new
 ArrayList();
InBlock.gif
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 Composite( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
public
 
override
 
void
 Add( Component component )
ExpandedSubBlockStart.gif  
{ children.Add( component ); }
InBlock.gif  
InBlock.gif  
public
 
override
 
void
 Remove( Component component )
ExpandedSubBlockStart.gif  
{ children.Remove( component ); }
InBlock.gif  
InBlock.gif  
public
 
override
 
void
 Display( 
int
 depth )
ExpandedSubBlockStart.gif  
InBlock.gif    Console.WriteLine( 
new
 String( 
'
-
'
, depth ) 
+
 name );
InBlock.gif
InBlock.gif    
//
 Display each of the node's children
InBlock.gif
    
foreach
( Component component 
in
 children )
InBlock.gif      component.Display( depth 
+
 
2
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Leaf"
None.gif
class
 Leaf : Component
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 Leaf( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
public
 
override
 
void
 Add( Component c )
ExpandedSubBlockStart.gif  
{ Console.WriteLine(
"
Cannot add to a leaf
"
); }
InBlock.gif
InBlock.gif  
public
 
override
 
void
 Remove( Component c )
ExpandedSubBlockStart.gif  
{ Console.WriteLine(
"
Cannot remove from a leaf
"
); }
InBlock.gif
InBlock.gif  
public
 
override
 
void
 Display( 
int
 depth )
ExpandedSubBlockStart.gif  
{ Console.WriteLine( 
new
 String( 
'
-
'
, depth ) 
+
 name ); }
ExpandedBlockEnd.gif}
None.gif
ExpandedBlockStart.gif
///
 
<summary>
InBlock.gif
///
 Client test
ExpandedBlockEnd.gif
///
 
</summary>
None.gif
public
 
class
 Client
ExpandedBlockStart.gif
{
InBlock.gif  
public
 
static
 
void
 Main( 
string
[] args )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
//
 Create a tree structure
InBlock.gif
    Composite root 
=
 
new
 Composite( 
"
root
"
 );
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf A
"
 ));
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf B
"
 ));
InBlock.gif    Composite comp 
=
 
new
 Composite( 
"
Composite X
"
 );
InBlock.gif
InBlock.gif    comp.Add( 
new
 Leaf( 
"
Leaf XA
"
 ) );
InBlock.gif    comp.Add( 
new
 Leaf( 
"
Leaf XB
"
 ) );
InBlock.gif    root.Add( comp );
InBlock.gif
InBlock.gif    root.Add( 
new
 Leaf( 
"
Leaf C
"
 ));
InBlock.gif
InBlock.gif    
//
 Add and remove a leaf
InBlock.gif
    Leaf l 
=
 
new
 Leaf( 
"
Leaf D
"
 );
InBlock.gif    root.Add( l );
InBlock.gif    root.Remove( l );
InBlock.gif
InBlock.gif    
//
 Recursively display nodes
InBlock.gif
    root.Display( 
1
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}

七、 使用合成模式时考虑的几个问题

  1. 明显的给出父对象的引用。在子对象里面给出父对象的引用,可以很容易的遍历所有父对象。有了这个引用,可以方便的应用责任链模式。
  2. 在通常的系统里,可以使用享元模式实现构件的共享,但是由于合成模式的对象经常要有对父对象的引用,因此共享不容易实现。
  3. 有时候系统需要遍历一个树枝结构的子构件很多次,这时候可以考虑把遍历子构件的结果暂时存储在父构件里面作为缓存。
  4. 关于使用什么数据类型来存储子对象的问题,在示意性的代码中使用了ArrayList,在实际系统中可以使用其它聚集或数组等。
  5. 客户端尽量不要直接调用树叶类中的方法,而是借助其父类(Component)的多态性完成调用,这样可以增加代码的复用性。

八、 和尚的故事

Pic62.gif

Pic63.gif

九、 一个实际应用Composite模式的例子

下面是一个实际应用中的程序,演示了通过一些基本图像元素(直线、园等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树的过程。

None.gif
//
 Composite pattern -- Real World example  
None.gif
None.gif
using
 System;
None.gif
using
 System.Collections;
None.gif
None.gif
//
 "Component"
None.gif
abstract
 
class
 DrawingElement
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
protected
 
string
 name;
InBlock.gif
InBlock.gif  
//
 Constructors
InBlock.gif
  
public
 DrawingElement( 
string
 name )
ExpandedSubBlockStart.gif  
this
.name 
=
 name; }
InBlock.gif 
InBlock.gif  
//
 Operation
InBlock.gif
  
abstract
 
public
 
void
 Display( 
int
 indent );
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Leaf"
None.gif
class
 PrimitiveElement : DrawingElement
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 PrimitiveElement( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Operation
InBlock.gif
  
public
 
override
 
void
 Display( 
int
 indent )
ExpandedSubBlockStart.gif  
{
InBlock.gif    Console.WriteLine( 
new
 String( 
'
-
'
, indent ) 
+
 
InBlock.gif      
"
 draw a {0}
"
, name );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}
None.gif
None.gif
//
 "Composite"
None.gif
class
 CompositeElement : DrawingElement
ExpandedBlockStart.gif
{
InBlock.gif  
//
 Fields
InBlock.gif
  
private
 ArrayList elements 
=
 
new
 ArrayList();
InBlock.gif 
InBlock.gif  
//
 Constructors
ExpandedSubBlockStart.gif
  
public
 CompositeElement( 
string
 name ) : 
base
( name ) 
{}
InBlock.gif
InBlock.gif  
//
 Methods
InBlock.gif
  
public
 
void
 Add( DrawingElement d )
ExpandedSubBlockStart.gif  
{ elements.Add( d ); }
InBlock.gif
InBlock.gif  
public
 
void
 Remove( DrawingElement d )
ExpandedSubBlockStart.gif  
{ elements.Remove( d ); }
InBlock.gif
InBlock.gif  
public
 
override
 
void
 Display( 
int
 indent )
ExpandedSubBlockStart.gif  
{
InBlock.gif    Console.WriteLine( 
new
 String( 
'
-
'
, indent ) 
+
InBlock.gif      
"
"
 
+
 name );
InBlock.gif
InBlock.gif    
//
 Display each child element on this node
InBlock.gif
    
foreach
( DrawingElement c 
in
 elements )
InBlock.gif      c.Display( indent 
+
 
2
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}
None.gif 
ExpandedBlockStart.gif
///
 
<summary>
InBlock.gif
///
  CompositeApp test
ExpandedBlockEnd.gif
///
 
</summary>
None.gif
public
 
class
 CompositeApp
ExpandedBlockStart.gif
{
InBlock.gif  
public
 
static
 
void
 Main( 
string
[] args )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
//
 Create a tree structure
InBlock.gif
    CompositeElement root 
=
 
new
  
InBlock.gif      CompositeElement( 
"
Picture
"
 );
InBlock.gif    root.Add( 
new
 PrimitiveElement( 
"
Red Line
"
 ));
InBlock.gif    root.Add( 
new
 PrimitiveElement( 
"
Blue Circle
"
 ));
InBlock.gif    root.Add( 
new
 PrimitiveElement( 
"
Green Box
"
 ));
InBlock.gif
InBlock.gif    CompositeElement comp 
=
 
new
  
InBlock.gif      CompositeElement( 
"
Two Circles
"
 );
InBlock.gif    comp.Add( 
new
 PrimitiveElement( 
"
Black Circle
"
 ) );
InBlock.gif    comp.Add( 
new
 PrimitiveElement( 
"
White Circle
"
 ) );
InBlock.gif    root.Add( comp );
InBlock.gif
InBlock.gif    
//
 Add and remove a PrimitiveElement
InBlock.gif
    PrimitiveElement l 
=
 
new
 PrimitiveElement( 
"
Yellow Line
"
 );
InBlock.gif    root.Add( l );
InBlock.gif    root.Remove( l );
InBlock.gif
InBlock.gif    
//
 Recursively display nodes
InBlock.gif
    root.Display( 
1
 );
ExpandedSubBlockEnd.gif  }
ExpandedBlockEnd.gif}

合成模式与很多其它模式都有联系,将在后续内容中逐步介绍。

参考文献:
阎宏,《Java与模式》,电子工业出版社
[美]James W. Cooper,《C#设计模式》,电子工业出版社
[美]Alan Shalloway  James R. Trott,《Design Patterns Explained》,中国电力出版社
[美]Robert C. Martin,《敏捷软件开发-原则、模式与实践》,清华大学出版社
[美]Don Box, Chris Sells,《.NET本质论 第1卷:公共语言运行库》,中国电力出版社

转载于:https://my.oschina.net/qihh/blog/57800

你可能感兴趣的文章
构建部署脚本
查看>>
排序算法 总结
查看>>
进程与系统调用
查看>>
深入理解HTTP Session
查看>>
一个十年没更新的项目还有没有必要玩
查看>>
Spring Boot 2.0(六):使用 Docker 部署 Spring Boot 开源软件云收藏
查看>>
MySQL高可用架构之MHA
查看>>
PlayScala实战 - 如何优雅地取出多层Future中的结果?
查看>>
Play For Scala 开发指南 - 第3章 常用类介绍
查看>>
Android开发效率—Eclipse快捷键
查看>>
iOS添加黑色蒙层
查看>>
Hibernate遇到的问题与解决方案
查看>>
hibernate 读取 blob 字段
查看>>
rsync简明手册
查看>>
Oracle存储过程-自定义数据类型,集合,遍历取值
查看>>
在Android上的使用代码覆盖工具
查看>>
MySQLdb安装 and 使用
查看>>
使 KVO在多个ViewController或者对象之间传值
查看>>
我的友情链接
查看>>
Python----Urllib的学习
查看>>