定义
组合模式将具有相同的基本类型的对象组合成树形结构的对象,该树的父节点和子节点具有相同的类型,相同的接口。换句话说,将对象组合成树形结构以表示“部分-整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。
由于父节点和子节点具有相同的基本类型,所以在整个树上不需要做任何类型检查,客户端就可以在父节点和子节点上进行相同的操作,而不需要区分它所需要操作的对象是父节点还是子节点。使用组合对象的客户端可以忽略树的父节点和字节点得差异,使得用起来非常顺手、简单,下面是一个运行时的组合对象结构的示例:
上述的组合对象是一个树形结构,不过并非一个二叉树,每个对象都是具有相同的接口,使得客户端看起来并无差异。类似的组合模式的静态结构类图如下图所示:
Component所定义的接口是类Leaf和Composite共享的。从上图的定义来看,显然有些接口Leaf并没有对应的意义,所以在图中的Leaf中并没有看到Component的全部接口,但是,你别以为Leaf不支持这些接口。虽然有些接口只有在Composite类里面有意义,比如上述类图中的add:Component、remove:Component等,但是这不妨碍类Leaf共享该接口,只不过类Leaf实现该方法是使用空方法而已(因为这些接口对Leaf毫无意义,只是为了和整个树保持统一而已)。
树的每个节点或表示一个叶子节点,或表示一个组合节点,他们的主要区别在于叶子节点没有组合节点的子节点。但是,因为叶子节点和组合节点共享一套接口,所以任何属于Component的操作都可以安全地适用于叶子节点和组合节点。
关于组合设计模式,其原定义如下所示:
适用场景
你如果有以下需求,不妨考虑考虑:
- 你希望客户端忽略组合对象与单个对象的不同,而是能够统一地使用组合结构中的所有对象
- 你希望表现出对象-层次的层次结构
- 通过定义包括基本对象和组合对象的层次结构,你可以使用简单的基本对象组合晨较为复杂的组合对象,而你还可以继续使用较为复杂的组合对象组合成更为复杂的对象,如此递归循环,你可以组成自己所需要的更复杂的结构对象。但是,对于客户端而言,使用简单对象和使用复杂组合对象是无差别的
- 简化客户单代码,同时使得创建同类型的复杂对象更简单。因为客户端不需要区分单个对象还是组合对象,所以不必写if-else之类的各种判断,而新对象也只需组合即可
Cocoa Touch中的组合模式
在Cocoa Touch框架中,UIViews是使用组合模式的树形结构组织管理的。每一个UIView对象都可以包含其他的UIView对象以构建一颗统一的树结构,使得客户端可以一同对待单个UIView对象和组合UIView对象。IOS程序中,window内的UIView对象是一颗内建的树形结构,其中树的根是UIWindow对象所包含的UIView对象,其他的UIView对象是它的子视图。虽然其他的是子视图,但是任何UIView对象都可以通过往该视图里面添加子视图而转变为父视图,任何UIView对象!UIView对象只能有一个父视图,但是允许有任意多个子视图。(树不就是这样?)UIView视图管理示意图如下所示:
上图的关系图可以使用更为清晰的树形示意图表示为:
组成树形结构的view对象也是事件处理和动作消息的响应链。
代码示例
//
// Component.h
// CompositeDemo
//
// Created by God Lin on 15/1/25.
// Copyright (c) 2015年 arbboter. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface UDComponent : NSObject
- (void) operation;
- (void) add:(UDComponent*)component;
- (void) remove:(UDComponent*)component;
- (NSInteger) getCount;
@end
//
// Component.m
// CompositeDemo
//
// Created by God Lin on 15/1/25.
// Copyright (c) 2015年 arbboter. All rights reserved.
//
#import "Component.h"
@interface UDComponent ()
@property (nonatomic, strong) NSMutableArray* children;
@end
@implementation UDComponent
- (id) init
{
if(self = [super init])
{
_children = [[NSMutableArray alloc] init];
}
return self;
}
- (void) operation;
{
NSLog(@"...do nothing.");
}
- (void) add:(UDComponent*)component
{
[self.children addObject:component];
}
- (void) remove:(UDComponent*)component
{
[self.children removeObject:component];
}
- (NSInteger) getCount
{
NSInteger sum = 1;
for (UDComponent* c in self.children)
{
sum += [c getCount];
}
return sum;
}
@end
这里的UDComponent定义了一些默认的方法,子类可选择是否直接使用,不过需要对应的声明变量_children.
//
// Composite.m
// CompositeDemo
//
// Created by God Lin on 15/1/25.
// Copyright (c) 2015年 arbboter. All rights reserved.
//
#import "Composite.h"
@interface Composite ()
// 为了简示Composite和Component内部的不同,定义不同的属性变量
// 存储子节点
@property (nonatomic, strong) NSMutableArray* sons;
@end
// 如果这里仍然像UDComponent使用children存储子节点,
// 那么这里可以直接使用父类的方法,不需要自己写
// 示例为了表现出内部的复杂性
@implementation Composite
- (id) init
{
if(self = [super init])
{
_sons = [[NSMutableArray alloc] init];
}
return self;
}
- (void) operation;
{
NSLog(@"child node count -> %ld", self.sons.count);
for (UDComponent* c in self.sons)
{
[c operation];
}
}
- (void) add:(UDComponent*)component
{
[self.sons addObject:component];
}
- (void) remove:(UDComponent*)component
{
[self.sons removeObject:component];
}
- (NSInteger) getCount
{
NSInteger sum = 1;
for (UDComponent* c in self.sons)
{
sum += [c getCount];
}
return sum;
}
@end
Composite重新自实现了一遍。
Leaf定义实现如下:
//
// Leaf.m
// CompositeDemo
//
// Created by God Lin on 15/1/25.
// Copyright (c) 2015年 arbboter. All rights reserved.
//
#import "Leaf.h"
// 无子节点,所以不需要定义存储子节点的变量
@implementation Leaf
- (void) operation;
{
NSLog(@"leaf node, no child");
}
- (void) add:(UDComponent*)component
{
// 该方法对Leaf无意义,空白实现
}
- (void) remove:(UDComponent*)component
{
// 该方法对Leaf无意义,空白实现
}
- (NSInteger) getCount
{
return 1;
}
@end
客户端测试使用的代码如下:
//
// main.m
// CompositeDemo
//
// Created by God Lin on 15/1/23.
// Copyright (c) 2015年 arbboter. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Leaf.h"
#import "Composite.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
// 生成本章的运行时对象结构图
// 从下往上生成
Composite* composite = nil;
Leaf* leaf = [[Leaf alloc] init];
composite = [[Composite alloc] init];
[composite add:leaf];
[composite add:[[Leaf alloc] init]];
[composite add:[[Leaf alloc] init]];
Composite* compositeFather = nil;
compositeFather = [[Composite alloc] init];
[compositeFather add:composite];
[compositeFather add:[[Leaf alloc] init]];
composite = compositeFather;
compositeFather = [[Composite alloc] init];
[compositeFather add:[[Leaf alloc] init]];
[compositeFather add:[[Leaf alloc] init]];
[compositeFather add:composite];
[compositeFather add:[[Leaf alloc] init]];
// 客户端使用组合对象
// 组合
[compositeFather operation];
// 单个对象
[leaf operation];
}
return 0;
}
可以看到,使用组合对象不管是构建一颗有层次的树,还是客户端可以简单地使用组合对象的对象(不需要区分Composite和Leaf对象),组合模式都有非常大得优势。