看图识模式
每个早晨出门前都要穿衣打扮,根据参加的场所选择不同的服饰。
比如现在有若干衣服:运动鞋、运动裤、卫衣、衬衫、西服、皮鞋、内衣等。
提出需求: 这周分别参加公益酒会、运动会、cosplay三个活动。怎么搭配这些衣服了,设计成类如何实现?
可以这样搭配,如图
继承方式
生成3个子类分别继承Person类:
- 第一个子类styleOne,披风+红内衣
- 第二个子类styleTwo,卫衣+运动鞋+短裤
- 第三个子类styleThree,西服+皮鞋+领带
看起来很好,而且解决了问题。但是发现同样也有一些问题,
- 这些子类都是静态的、不可改变的,比如天冷了我需要加一件棉服怎么办?每个子类都要改变
- 一年四季我们穿的衣服千变万化,这样需要创建多少个子类呢?肯定是一个很恐怖的数量
一个子类
在一个子类里定义全部的功能,需要穿哪件衣服(哪个功能)就穿哪件衣服(调用对应功能),但是这不满足单一职责
原则(不同的功能不应该放在同一个类里面),而且这个类会过于臃肿而无法维护,并且大部分功能是使用不到的,只有在相应的场景才会需要。
而且如果增加新功能也要改变这里类,也不符合开放-封闭
原则。
分析&解决
通过上面的例子我们可以发现2个规律
-
第一: 穿的衣服是可以任意组合的,理论上穿几件、穿哪种类型都可以。
也就是动态添加
-
第二: 穿完一件衣服可以再穿另一件衣服,穿完一件衣服后我还是我(类型没发生变化)。穿一件衣服之前不用关心我穿没穿衣服、穿了几件衣服,穿完之后同样也是。
添加之后类型不发生变化,添加前后都可以被一致对待。也就是装饰前和装饰后 没有什么不同
为了实现这些目的,可以这样设计:
首先,声明一个抽象接口Person,它有一个show方法来展示当前的穿着打扮。具体的人(Person)实现这个接口比如黄种人(YellowMan),show方法只输出人名,在未装饰之前就只是一个单纯的人。
然后再定义一个装饰类Decorator,它也实现接口Person,但不同的是它拥有一个具体对象(YellowMan)的引用,而且多了一个addBehavior方法,这个方法里实现对具体对象的装饰(添加职责)。
最后创建具体的Decorator类,实现具体的addBehavior方法。
把一个具体的人类(Person)传递创建一个具体的装饰类,由于装饰类(Decorator)和人类(Person)拥有相同的接口,所以它俩的对外使用是一致的。当调用show的时候,通过对具体人类(Person)的引用调用它对应的show方法,同时调用装饰方法(addBehavior),达到了添加职责的目的。
看一下设计类图:
这样我们就可以把任意的装饰类连接起来使用,用图表示应该是这样的
有几件衣服(职责),就创建几个装饰类,具体怎么穿就可以随意搭配了。下面看一下代码怎么写?
代码示例
抽象接口Person
@protocol Person <NSObject>
- (void)show;
@end
具体人YellowMan实现这个接口Person
#import "Person.h"
@interface YellowMan : NSObject <Person>
- (instancetype)initWithName:(NSString *)name;
- (void)show;
@end
@interface YellowMan ()
@property (nonatomic, copy) NSString *name;
@end
@implementation YellowMan
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
_name = name;
}
return self;
}
- (void)show
{
NSLog(@"我是: %@", self.name);
}
@end
定义一个装饰类Decorator
@interface Decorator : NSObject <Person>
- (instancetype)initWithPerson:(id <Person>)person;
- (void)show;
@end
@interface Decorator ()
@property (nonatomic, strong) id <Person>person;
@end
@implementation Decorator
- (instancetype)initWithPerson:(id <Person>)person
{
self = [super init];
if (self) {
_person = person;
}
return self;
}
- (void)show
{
[self.person show];
}
@end
定义具体的装饰类: 衬衫ShirtDecorator
@interface ShirtDecorator : Decorator
@end
@implementation ShirtDecorator
- (void)show
{
[super show];
[self addBehavior];
}
- (void)addBehavior
{
NSLog(@"-- 穿衬衫");
}
@end
定义具体的装饰类: 西装SuitDecorator
@interface SuitDecorator : Decorator
@end
@implementation SuitDecorator
- (void)show
{
[super show];
[self addBehavior];
}
- (void)addBehavior
{
NSLog(@"-- 穿西装");
}
@end
其它的装饰类都类似,不再一一写了;
client调用
YellowMan *aMan = [[YellowMan alloc] initWithName:@"小明"];
ShirtDecorator *shirtA = [[ShirtDecorator alloc] initWithPerson:aMan];
SuitDecorator *suitA = [[SuitDecorator alloc] initWithPerson:shirtA];
[suitA show];
// 小李是超人,内衣穿外面
YellowMan *bMan = [[YellowMan alloc] initWithName:@"小李"];
ShirtDecorator *shirtB = [[ShirtDecorator alloc] initWithPerson:bMan];
SuitDecorator *suitB = [[SuitDecorator alloc] initWithPerson:shirtB];
UnderwearDecorator *underwear = [[UnderwearDecorator alloc] initWithPerson:suitB];
[underwear show];
运行结果:
我们发现被装饰过的对象任然和没装饰前的一样,它的功能没有发生改变,只是多了被装饰的功能,使用方式也没有发生变化。
而且被装饰后的对象还可以被继续装饰,装饰多少次和装饰顺序完全可以动态控制。
模式定义
定义
装饰模式: 动态地给一个对象添加一些额外的职责。就拓展功能来说,装饰模式相比生成子类更为灵活。
结构图
装饰模式包含如下角色:
- 抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责
- ConcreteComponent: 具体构件,也可以给这个对象添加一些职责
- Decorator: 抽象装饰类,继承了Component,维持一个指向构件Component对象的实例,并定义一个与抽象组件角色Component接口一致的接口
- ConcreteDecorator: 具体装饰类,起到给Component添加职责的功能。
通过上面的结构我们发现:
- 装饰者和被装饰对象有相同的超类型。
- 你可以用一个或多个装饰者包装一个对象。
- 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合 ,可以用装饰过的对象代替它。
- 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
类应该对扩展开放,对修改关闭
特征
目的
扩展对象的功能
装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
使用场景
在以下情况下可以使用装饰模式:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类,object-c中不会有这种情况)
透明性
装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型,而应该全部声明为抽象构件类型;
例如:
// 应该这样
id <Person>person = [[YellowMan alloc] initWithName:@"小明"];
id <Person>shirtPerson = [[ShirtDecorator alloc] initWithPerson:person];
id <Person>suitPerson = [[SuitDecorator alloc] initWithPerson:shirtPerson];
[suitPerson show];
// 不应该这样
YellowMan *aMan = [[YellowMan alloc] initWithName:@"小明"];
ShirtDecorator *shirtA = [[ShirtDecorator alloc] initWithPerson:aMan];
SuitDecorator *suitA = [[SuitDecorator alloc] initWithPerson:shirtA];
[suitA show];
半透明装饰模式允 许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。
然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法。
优缺点
优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
- 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
- 产生很多具体装饰类。这些装饰类和它们之间相互连接的方式将增加系统的复杂度,加大学习与理解的难度。
Objective-C中的应用
根据Objective-C的特性,有两种实现方式:
- 通过真正的子类实现装饰,上面通用的结构
- 通过分类Category实现装饰
第二种方式是使用了Objective-C的语言功能,通过分类向类添加行为,不必进行子类化,这并非标准的装饰模式结构,但是实现了装饰模式同样的需求。尽管使用分类来实现装饰模式跟原始风格有偏离,但是实现少量的装饰器的时候,它比真正子类方式更加轻量、更加容易。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。