设计模式(8) 组合模式(COMPOSITE)

问题聚焦:
   结构型模式的本质是:对象组合。
    而本节所展示的模式,就是典型的结构型模式。
    那对象组合成什么结构呢?组合模式主要将对象组合成树形结构,表示”部分-整体“的层次结构。

    

意图:
将对象组合成树形结构,以表示”整体-部分“的层次结构。
组合模式使得用户对单个对象和组合对象的使用具有一致性。

动机:
以绘图编辑器和图形捕捉系统这样的图形应用程序为例。
设计者可以组合多个简单组件以形成一些较大的组件,这些组件又可以组合形成更大的组件。这种暴力组合方式,也是我们经常使用的。
设计:为Text和Line这样的图元定义一些类,另外定义一些类作为这些图元的容器类。
这种方法存在的问题:使用这些类的代码必须区别对待图元对象与容器对象
组合模式的处理方法:递归组合
用组合模式吹的关键:一个抽象类,既可以代表图元,又可以代表图元的容器。
设计:
  • 抽象类(Graphic,声明一些特定特性对象相关的操作,如Draw,和所有组合对象共享的操作)
  • 子类Line,Rectangle和Text定义了一些图元对象
  • Picture类定义了一个Graphic对象的聚合,Picture的Draw操作是通过对它的子部件调用Draw实现,Picture还用这种方法实现了一些与其子部件相关的操作。同时,由于Picture接口与Graphic接口是一致的,因此Picture对象可以递归地组合其他Picture对象
设计模式(8)  组合模式(COMPOSITE)
设计模式(8)  组合模式(COMPOSITE)
一个典型的由递归组合的Graphic对象组成的组合对象结构如下图所示:
设计模式(8)  组合模式(COMPOSITE)
设计模式(8)  组合模式(COMPOSITE)


适用性:
一下情况使用组合模式:
  • 表示对象的部分-整体层次结构
  • 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象
结构:
设计模式(8)  组合模式(COMPOSITE)
设计模式(8)  组合模式(COMPOSITE)
典型的组合模式对象结构:
设计模式(8)  组合模式(COMPOSITE)
设计模式(8)  组合模式(COMPOSITE)
参与者:
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类中的操作和行为局部化。


参考资料:
《设计模式:可复用面向对象软件的基础》


设计模式(8) 组合模式(COMPOSITE)

上一篇:寒假学习 第15天 (linux 高级编程) 笔记 总结


下一篇:对象.delegate=self的理解