问题聚焦:
结构型模式的本质是:对象组合。
而本节所展示的模式,就是典型的结构型模式。
那对象组合成什么结构呢?组合模式主要将对象组合成树形结构,表示”部分-整体“的层次结构。
意图:
将对象组合成树形结构,以表示”整体-部分“的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有一致性。
动机:
以绘图编辑器和图形捕捉系统这样的图形应用程序为例。
设计者可以组合多个简单组件以形成一些较大的组件,这些组件又可以组合形成更大的组件。这种暴力组合方式,也是我们经常使用的。
设计:为Text和Line这样的图元定义一些类,另外定义一些类作为这些图元的容器类。
这种方法存在的问题:使用这些类的代码必须区别对待图元对象与容器对象
组合模式的处理方法:递归组合
用组合模式吹的关键:一个抽象类,既可以代表图元,又可以代表图元的容器。
设计:
- 抽象类(Graphic,声明一些特定特性对象相关的操作,如Draw,和所有组合对象共享的操作)
- 子类Line,Rectangle和Text定义了一些图元对象
- Picture类定义了一个Graphic对象的聚合,Picture的Draw操作是通过对它的子部件调用Draw实现,Picture还用这种方法实现了一些与其子部件相关的操作。同时,由于Picture接口与Graphic接口是一致的,因此Picture对象可以递归地组合其他Picture对象
一个典型的由递归组合的Graphic对象组成的组合对象结构如下图所示:
适用性:
一下情况使用组合模式:
- 表示对象的部分-整体层次结构
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
典型的组合模式对象结构:
参与者:
1 Component(Graphic):
- 为组合中的对象声明接口
- 在适当情况下,实现所有类共有接口的缺省行为
- 声明一个接口用于访问和管理组合中的子组件
- 在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它
2 Leaf(Rectangle, Line, Text)
- 在组合中表示叶节点对象,叶节点没有子节点
- 在组合中定义图元对象的行为
3 Compsite(Picture)
- 定义有子部件的那些部件的行为
- 存储子部件
- 在组合模式接口中实现与子部件有关的操作
4 Client
- 通过Component接口操纵组合部件的对象
用户使用组合模式类接口与组合结构中的对象进行交互
如果接收者是一个叶节点,则直接处理请求;如果接收者是Composite,它通常将请求发送给它的子部件,在转发请求之前后之后可能执行的一些辅助操作
效果:
- 定义了包含基本对象和组合对象的类层次结构:递归组合
- 简化客户代码:一致地使用组合结构和单个对象
- 使得更容易增加新类型的组件:方便新增组合结构或者叶节点
- 使得你的设计变得更加一般化
1 显式的父部件引用
保持从子部件到父部件的引用能简化组合结构的遍历和管理。
2 共享组件
共享组件可以减少对内存的需求。
3 最大化Component接口
组合模式的目的之一就是使得用户不知道他们正在使用的具体的Leaf类还是Composite类。
为了达到这一目的,Composite类应为Leaf类和Composite类尽可能多定义一些公共操作。
4 声明管理子部件的操作
管理子部件的操作放置位置的选择:
透明性:将管理子部件的操作放在抽象类Component中,虽然这些操作对Leaf类没有意义。
安全性:在组合对象中放置管理子部件的操作,因为只有组合对象才有子部件。
如何选择取决于如何取舍。
5 组合模式是否应该实现一个Component列表
由于叶节点并没有子节点,所以如果节点数目较少时,可以考虑将子节点结合定义为一个实例变量,然后声明一些操作对子节点进行访问和管理。
6 子部件排序
如果考虑子节点的顺序,必须仔细地设计对子节点的访问和管理接口,以便管理子节点序列。
后面所介绍的迭代器模式(Iterator)会在这方面给予一些知道。
7 使用高速缓冲存储改善性能
如果你需要对组合进行频繁的遍历或查找,Composite类可以缓冲存储对它的子节点进行遍历或查找的相关信息。
8 应该由谁删除Component
在没有垃圾回收机制的语言中,当一个Composite被销毁时,通常最好由Composite负责删除其子节点。如果Leaf对象被共享,则可以不删除(当然,这时候又可能需要引用计数来管理这些共享组件)。
9 存储组件最好用哪一种数据结构
数据结构的选择取决于效率:列表,树,数组和hash表都是可选择的。
代码示例:
计算机,音响或者汽车底盘设备有很明显的部分-整体层次结构或者容器层次结。
这种结构很自然地使用组合模式进行抽象。
类设定:
Equipment声明一些操作返回一个设备的属性
子类为指定的设备实现这些操作,包括磁盘驱动器,集成电路,开关的Leaf类。
Equipment还声明了一个CreateIterator操作,该操作为访问它的零件返回一个Iterator。
class Equipment { public: virtual ~Equipment(); const char* Name() { return _name }; virtual Watt Power(); virtual Currency NetPrice(); virtual Currency DiscountPrice(); virtual void Add(Equipment*); virtual void Remove(Equipment*); virtual Iterator<Equipment*>* CreateIterator(); protected: Equipment(const char*); private: const char* _name; };
磁盘驱动器(叶节点Leaf)
class FloppyDisk : public Equipment { public FloppyDisk(const char*); virtual ~FloppyDisk(); virtual Watt Power(); virtual Currency NetPrice(); virtual Currency DiscountPrice(); };
CompositeEquipment为包含其他设备的基类,同时也是Equipment的子类。
class CompositeEquipment : public Equipment { public: virtual ~CompositeEquipment(); virtual Watt Power(); virtual Currency NetPrice(); virtual Currency DiscountPrice(); virtual void Add(Equipment*); virtual void Remove(Equipment*); virtual Iterator<Equipment*>* CreateIterator(); protected: CompositeEquipment(const char*); private: List<Equipment*> _equipment; }; // CompositeEquipment为访问和管理子设备定义了一些操作 // 操作Add和Remove从存储在_equipment成员变量中的设备列表中插入并删除设备 // 操作CreateIterator返回一个迭代器遍历这个列表。 // NetPrice的缺省实现使用CreateIterator类累加子设备的实际价格。 Currency CompositeEquipment::NetPrice () { Iterator<Equipment*>* i = CreateIterator(); Currency total = 0; for (i->First(); ~i->IsDone(); i->Next()) { total += i->CurrentItem()->NetPrice(); } delete i; return total; } // 现在我们将计算机的底盘表示为CompositeEquipment的子类Chassis class Chassis : public CompositeEquipment { public: Chassis(const char*); virtual ~Chassis(); virtual Watt Power(); virtual Currency NetPrice(); virtual Currency DiscountPrice(); };
类设定完毕,包括单个零件和一些包含若干零件的组合零件。下面就可以对计算机进行组装。
Cabinet* cabinet = new Cabinet("PC Cabinet"); // 机箱节点 Chassis* chassis = new Chassis("PC Cabinet"); // 主板节点 cabinet->Add(chassis); // 将主板组装到机箱上去 Bus* bus = new Bus("MAC Bus"); // 总线节点 bus->Add(New Card("16Mbs Token Ring")); //总线上的某个零部件 chassis->Add(bus); //主板挂载总线 chassis->Add(new FloppyDisk("3.5in Floppy")); // 主板挂载硬盘 cout << "The net price is " << chassis->NetPrice() << endl;
相关模式:
通常部件-父部件连接用于Responsibility of Chain模式
Decorator模式经常与Composite模式一起使用。
Flyweight模式让你共享组件
Iterator模式可用来遍历Composite
Visitor模式将本来应该分布在Composite和Leaf类中的操作和行为局部化。
参考资料:
《设计模式:可复用面向对象软件的基础》