Delphi合成模式(Composite模式)
作者 陈省
合成模式应该说是一种应用范围非常广的模式,最常见的莫过于树视图了,每个树视图的节点都可以添加子节点,不断反复就可以构成非常复杂的系统,常见的应用有菜单系统,文件系统都是基于树状结构的。下面就是一个树状的菜单示意图:

此外,在VCL中,窗体及其上面的组件也使用了合成模式。每个控件的Parent属性都对应于界面树的父节点,控件的Controls[index]属性则对应着该控件在界面树中的子节点。在IDE中,我们可以使用Object Treeview来察看窗体的树结构:

复合模式的实现
下面是复合模式的UML示意图:

其中TCompositeLeaf是叶子节点类,特点就是不能有下级的节点,这种叶子节点类型很常见,比如文件系统中的文件下面不能再有文件了。而TCompositeBranch则是树枝节点类,特点就是可以有下级叶子节点或者下级树枝节点,原型可以对应到文件系统中的目录,目录下可以有子目录以及文件。为了能够对下级节点进行管理,因此TCompoisteBranch类需要定义AddChild,DelChild方法来添加删除子节点,以及ChildrenCount函数返回子节点数目,进而通过GetChild方法通过索引遍历子节点。因为树枝节点下即可以有叶子节点也可以有树枝节点,正如文件会有很多不同的种类,叶子和树枝节点也可能还会有很多的种类,如果针对不同的节点都编写不同的增加,删除等操作方法,类的方法就会过分膨胀。为了让用户能够忽略节点对象的不同,以一种统一的方式操作合成模式中的对象,这里将叶子节点和树枝节点抽象出一个共同的父类TComposite,这样使用AddChild等方法的参数只要统一是TComposite类型就可以了。另外GetParent方法用来获得节点的父节点,如果为了便于将节点从一个位置移动到另一个位置,可以考虑增加SetParent方法。
安全和透明的合成模式
上面我们给出的UML是一种安全的合成模式,之所以称之为安全,是因为叶子节点类TCompositeLeaf没有AddChild等管理子节点的方法,这样用户在编译期误调用了叶子节点的AddChild方法时会报错,就会避免错误。但是使用安全的合成模式有一个问题就是系统中的对象不具有一致的使用方法,叶子节点对象和树枝节点对象具有不同的接口,缺乏透明性。那么经常我们也会看到下面这样的透明的合成模式:

可以看到这回叶子节点对象也有AddChild的方法了。透明方式的合成模式特点就是不安全,因为叶子节点的AddChild等方法是无意义的,但是客户可能会误调用了叶子节点对象的AddChild方法,解决的办法是重载TCompositeLeaf类的AddChild等方法,在运行时抛出异常,但是运行时抛出异常的办法显然有局限性,如果在测试时,代码没有运行到相应的分支,我们就无法知道这里进行了错误的调用,而编译期技术则保证该错误一定会被发现。
不过从VCL的中合成模式的实现来看,Delphi的R & D Team倾向于透明的合成模式,比如TPanel作为容器来看相当于树枝节点,可以在面板中包含儿子控件,而TButton相当于叶子控件,不能有儿子控件,但是TButton和TPanel同样具有InsertControl和RemoveControl等管理子控件的方法。不过有趣的是TButton并没有重载InsertControl方法以便在插入子组件时抛出异常,而是直接使用了基类的InsertControl方法,也就是说在运行时我们可以通过调用InsertControl在TButton中插入其它的组件,这应该说是VCL中一个不严谨的实现吧。