如何给类或者对象增加行为?
- 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同事还拥有父类的方法.但这种方法是静态的,用户不能控制增加行为的方式和时机
- 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来觉得是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)
模式定义
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰器模式比生成子类实现更为灵活.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
模式结构和说明
Component:组件对象的接口,可以给这些对象动态的添加职责
ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职能
Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并只有一个Component对象,其实就是持有一个被装饰的对象.注意,这个被装饰的对象不一定是最原始的那个对象了,也可能是被其它装饰器装饰过后的对象,反正都是实现的同一个接口,也就是同一类型。
ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。
示例实现
考虑这样一个场景,现在有一个煎饼摊,人们去买煎饼(Pancake),有些人要加火腿(Ham),有些人要加鸡蛋(Egg),有些人要加生菜(Lettuce),有些人要加其中的几样
不用模式的实现
要做一个煎饼首先得知道客人要加什么,是按客人的要求来做的
- 首先要把所有材料的准备出来
- (void)addEgg
{
NSLog(@"添加鸡蛋");
}
- (void)addHam
{
NSLog(@"添加火腿");
}
- (void)addLettuce
{
NSLog(@"添加生菜");
}
- (void)cook
{
NSLog(@"做一个煎饼");
}
- 然后根据客人的需要去定做
- (void)cookPancakeWithEgg:(BOOL)egg ham:(BOOL)ham lettuce:(BOOL)lettuce
{
if (egg) {
[self addEgg];
}
if (ham) {
[self addHam];
}
if (lettuce) {
[self addLettuce];
}
[self cook];
}
- 客户端测试
Pancake *pancake = [[Pancake alloc] init];
[pancake cookPancakeWithEgg:YES ham:NO lettuce:YES];
- 结果展示
上面做出了一个添加鸡蛋和生菜的煎饼,看到这里你会说没什么问题啊,不论客人需要加什么我都可以实现,这儿有两个问题:- 如果现在的食材再添加牛排,鸡柳呢(口水在流啊)
- 制作煎饼的时候一堆if判断,如果食材发生改变,里面的代码就得改动,严重违反"开闭原则"
那怎么解决呢?咱们把上面的问题抽象一下,假设我就做一个煎饼,至于加食材,只是按照需要给它灵活的增加功能,还能动态的组合,那么问题来了,如何才能够透明的给一个对象增加功能,并实现功能的动态组合呢?
用来解决上述问题的一个合理解决方案就是使用装饰模式
装饰模式解决
回归上面的问题:透明的给一个对象增加功能,并实现功能的动态组合
所谓透明的给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象
要想实现透明的给一个对象增加功能,也就是要扩展对象的功能了,使用继承啊,有人马上提出了一个方案,但很快就被否决了,那要减少或者修改功能呢?事实上继承是非常不灵活的复用方式。那就用“对象组合”嘛,又有人提出新的方案来了,这个方案得到了大家的赞同。
在装饰模式的实现中,为了能够和原来使用被装饰对象的代码实现无缝结合,是通过定义一个抽象类,让这个类实现与装饰对象相同的接口,然后再具体实现类里面,转调被装饰的对象,在转调的前后添加新的功能,这就实现了被装饰对象增加功能,这个思路跟"对象组合"非常相似
在转调的时候,如果觉得被装饰的对象的功能不再需要了,还可以直接替换掉,也就是不再转调,而是在装饰对象里面完全全新的实现。
装饰模式示例代码
- 煎饼的组件接口和基本的实现对象
//抽象类,定义一个煎饼接口
@interface AbPancake : NSObject
- (void)cook;
@end
- 为煎饼定义一个基本的实现
//具体的煎饼类
@interface IPancake : AbPancake
@end
@implementation IPancake
- (void)cook
{
NSLog(@"的煎饼");
}
@end
- 定义抽象的装饰器类
先定义出各个装饰器公共的父类,在这里定义所有装饰器对象需要实现的方法。这个父类应该实现组件的接口,这样才能保证装饰后的对象仍然可以继续被装饰
@interface Decorator : AbPancake
/* 持有被装饰的组件对象 */
@property (nonatomic,strong) AbPancake *pancake;
//通过构造方法传入被装饰的对象
+ (instancetype)decoratorWithComponent:(AbPancake *)pancake;
@end
@implementation Decorator
+ (instancetype)decoratorWithComponent:(AbPancake *)pancake
{
Decorator *decorator = [[[self class] alloc] init];
decorator.pancake = pancake;
return decorator;
}
- (void)cook
{
[self.pancake cook];
}
@end
- 定义一系列的装饰器对象
用一个具体的装饰器对象来实现一个规则,添加一种食材
@interface EggDecorator : Decorator
@end
- (void)cook
{
NSLog(@"加一个鸡蛋");
[super cook];
}
…其余的按上面的鸡蛋装饰器写法
- 客户端使用
//先创建基本的煎饼对象,这也是被装饰的对象
AbPancake *ipancake = [[IPancake alloc] init];
AbPancake *egg = [EggDecorator decoratorWithComponent:ipancake];
AbPancake *ham = [HamDecorator decoratorWithComponent:egg];
[ham cook];
从结果看出,依次调用相应的装饰器来执行业务功能是一个递归调用的方法
如上面的示例,把不同的煎饼食材,放在不同的装饰器对象里面,采用动态组合的方式,来产生客户想要的东西,这种方式明显比增加子类来的更为灵活,因为装饰模式的起源点是采用对象组合的方式,然后再组合的时候顺便增加些功能.为了达到一层一层组装的效果,装饰模式还要求装饰器要实现与被装饰对象相同的业务接口,这样才能以同一种方式依次组合下去。
灵活性还体现在动态上,如果是继承的方式,那么所有的类实例都有这个功能了,而采用装饰模式,可以动态的为某几个对象实例添加功能,而不是对整个类添加功能。
模式讲解
模式功能
**装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观。**当装饰过后,从外部使用系统的角度看,就不再是使用原始的那个对象了,而是使用被一系列的装饰器装饰过后的对象。
这样就能够灵活的改变一个对象的功能,只要动态组合的装饰器发生了改变,那么最终所得到的对象的功能也就发生了改变。
还有另外一个好处,那就是装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器装饰不同的对象,比如上面示例中,有客人想要两鸡蛋
对象组合
刚开始前面已经讲到了,一个类的功能的扩展方式,可以是继承,也可以是功能更强大、更灵活的对象组合的方式。
继承大家很熟悉,不多说,下面来说说对象组合
举例来说吧,假若有一个对象A,实现了一个a1的方法,而C1对象想要来扩展A的功能,给它增加一个c11的方法,那么一个方案是继承,A对象示例代码如下:
public class A {
public void a1(){
System.out.println("now in A.a1");
}
}
C1对象示例代码如下:
public class C1 extends A{
public void c11(){
System.out.println("now in C1.c11");
}
}
另外一个方案就是使用对象组合,怎么组合呢?就是在C1对象里面不再继承A对象了,而是去组合使用A对象的实例,通过转调A对象的功能来实现A对象已有的功能,写个新的对象C2来示范,示例代码如下:
public class C2 {
/**
* 创建A对象的实例
*/
private A a = new A();
public void a1(){
//转调A对象的功能
a.a1();
}
public void c11(){
System.out.println("now in C2.c11");
}
}
大家想想,在转调前后是不是还可以做些功能处理呢?对于A对象是不是透明的呢?对象组合是不是也很简单,而且更灵活了:
首先可以有选择的复用功能,不是所有A的功能都会被复用,在C2中少调用几个A定义的功能就可以了;
其次在转调前后,可以实现一些功能处理,而且对于A对象是透明的,也就是A对象并不知道在a1方法处理的时候被追加了功能;
还有一个额外的好处,就是可以组合拥有多个对象的功能,假如还有一个对象B,而C2也想拥有B对象的功能,那很简单,再增加一个方法,然后转调B对象就好了;
装饰器
装饰器实现了对被装饰对象的某些装饰功能,可以在装饰器里面调用被装饰对象的功能,获取相应的值,这其实是一种递归调用
在装饰器里不仅仅是可以给装饰对象增加功能,还可以根据需要选择是否调用被装饰对象的功能,如果不调用被装饰对象的功能,那就变成完全重新实现了,相当于动态修改了被装饰对象的功能
另外一点,各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合的时候,才没有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的,否则会大大降低装饰器组合的灵活性。
装饰器和组件类的关系
装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。
组件类是不知道装饰器的存在的,装饰器给组件添加功能是一种透明的包装,组件类毫不知情。需要改变的是外部使用组件类的地方,现在需要使用包装后的类,接口是一样的,但是具体的实现类发生了改变。
总结
关于装饰器模式的使用,主要有以下几点需要注意的:
- 抽象装饰器和具体被装饰的对象实现同一个接口
- 抽象装饰器里面要持有接口对象,以便请求传递
- 具体装饰器覆盖抽象装饰器方法并用super进行调用,传递请求