一、概念
1、组合模式的动机
树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题。
组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)。
2、组合模式的定义
组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
3、组合模式的3个角色
1)Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
2)Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
3)Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
4、结构图
二、示例
1)创建一个Company协议,里面有一些公司的添加和删除方法,表示Component抽象构件;
2)创建ConcreteCompany类,遵循Company协议,并实现协议的方法,表示Composite容器构件;
3)创建三个类HRDepartment、ITDepartment和ServiceDepartment,都遵循Company协议,并实现协议的方法,表示Leaf叶子构件;
Company协议:
// 抽象构件Component
@protocol Company <NSObject>
@property(nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name;
- (void)add:(id<Company>)company;
- (void)remove:(id<Company>)company;
- (void)work;
@end
typedef id<Company> Company;
ConcreteCompany类:
// 容器构件Composite
// .h文件
@interface ConcreteCompany : NSObject<Company>
@end
// .m文件
@interface ConcreteCompany ()
@property(nonatomic, strong) NSMutableArray *childList;
@end
@implementation ConcreteCompany
@synthesize name = _name;
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_childList = [NSMutableArray array];
_name = name;
}
return self;
}
- (void)add:(id<Company>)company {
[self.childList addObject:company];
}
- (void)remove:(id<Company>)company {
if ([self.childList containsObject:company]) {
[self.childList removeObject:company];
}
}
- (void)work {
NSLog(@"%@-----------", self.name);
for (Company company in self.childList) { //递归遍历
[company work];
}
}
@end
HRDepartment、ITDepartment和ServiceDepartment类:
// 叶子构件Leaf
// HRDepartment
@interface HRDepartment : NSObject<Company>
@end
@implementation HRDepartment
@synthesize name = _name;
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)add:(id<Company>)company {}
- (void)remove:(id<Company>)company {}
- (void)work {
NSLog(@"%@:give an offer", self.name);
}
@end
// ITDepartment
@interface ITDepartment : NSObject<Company>
@end
@implementation ITDepartment
@synthesize name = _name;
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)add:(id<Company>)company {}
- (void)remove:(id<Company>)company {}
- (void)work {
NSLog(@"%@:coding", self.name);
}
@end
// ServiceDepartment
@interface ServiceDepartment : NSObject<Company>
@end
@implementation ServiceDepartment
@synthesize name = _name;
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)add:(id<Company>)company {}
- (void)remove:(id<Company>)company {}
- (void)work {
NSLog(@"%@:call customers", self.name);
}
@end
运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
ConcreteCompany *apple = [[ConcreteCompany alloc] initWithName:@"苹果公司"];
ConcreteCompany *AmericanCompany = [[ConcreteCompany alloc] initWithName:@"美国分公司"];
ConcreteCompany *ChinaCompany = [[ConcreteCompany alloc] initWithName:@"中国分公司"];
[apple add:AmericanCompany];
[apple add:ChinaCompany];
[ChinaCompany add:[[ServiceDepartment alloc] initWithName:@"中国分部客服部门"]];
[AmericanCompany add:[[HRDepartment alloc] initWithName:@"美国分部HR部门"]];
[AmericanCompany add:[[ITDepartment alloc] initWithName:@"美国分部IT部门"]];
[AmericanCompany add:[[ServiceDepartment alloc] initWithName:@"美国分部客服部门"]];
[apple work];
// [AmericanCompany work];
// [ChinaCompany work];
}
打印结果:
苹果公司-----------
美国分公司-----------
美国分部HR部门:give an offer
美国分部IT部门:coding
美国分部客服部门:call customers
中国分公司-----------
中国分部客服部门:call customers
三、总结
组合模式使用面向对象的思想来实现树形结构的构建与处理,描述了如何将容器对象和叶子对象进行递归组合,实现简单,灵活性好。由于在软件开发中存在大量的树形结构,因此组合模式是一种使用频率较高的结构型设计模式。
1、优点
1、组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
2、客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
3、在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
4、组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
2、缺点
在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。
3、适用场景
1、在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
2、在一个使用面向对象语言开发的系统中需要处理一个树形结构。
3、在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
4、iOS应用举例
在Cocoa Touch框架中,UIView被组织成一个组合结构,每个UIView的实例可以包含UIView的其他实例,形成统一的树形结构,让客户端对单个UIView对象和UIView的组合统一对待。
视图的树形结构的根部是一个UIWindow对象和它的内容视图,添加进来的其他UIView是它的子视图,被添加的子视图可以包含其他的视图而变成自己的子视图的父视图。UIView对象只能有一个父视图,可以有零到多个子视图。
Demo地址:iOS-Design-Patterns