定义
装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
3.3. 模式结构
装饰模式包含如下角色:
- Component: 抽象构件
- ConcreteComponent: 具体构件
- Decorator: 抽象装饰类
- ConcreteDecorator: 具体装饰类
使用情景
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 需要动态地给一个对象增加功能,这些功能也可以动态地被撤销。
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。不能采用继承的情况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;第二类是因为类定义不能继承(如final类).
代码分析
为星巴克开发一个结账买单的系统
- 咖啡本身的种类不同(比如,咖啡可以分为浓缩咖啡(Espresso)、无咖啡因咖啡(Decaf)、深度烘焙咖啡(DarkRoast)等)。
- 咖啡里加的调料(牛奶、豆浆、抹茶、摩卡等)不同。
所以,我们可以将问题简化成:咖啡饮料的价格 = 咖啡本身的价格 + 各种调料的价格
比如,Espresso Macchiato(浓缩玛奇朵)的价格 = Espresso(浓缩咖啡) 的价格 +Milk(牛奶)的价格 + Mocha(摩卡)的价格。
Beverage 协议
Beverage.h
#import <Foundation/Foundation.h>
@protocol Beverage <NSObject>
@optional
- (NSString *)getName;
- (double)cost;
@end
Espresso 类
Espresso.h
#import <Foundation/Foundation.h>
#import "Beverage.h"
@interface Espresso : NSObject<Beverage>
@end
//Espresso.m
#import "Espresso.h"
@implementation Espresso{
NSString *_name;
}
- (instancetype)init{
if (self = [super init]) {
_name = @"Espresso";
}
return self;
}
- (NSString *)getName{
return _name;
}
- (double)cost{
return 1.99;
}
@end
CondimentDecorator 协议
#import <Foundation/Foundation.h>
#import "Beverage.h"
@protocol CondimentDecorator <Beverage>
@end
Milk 类
#import <Foundation/Foundation.h>
#import "Beverage.h"
#import "CondimentDecorator.h"
@interface Milk : NSObject <CondimentDecorator>
@property (strong, nonatomic)id<Beverage> beverage;
- (instancetype)initWithBeverage:(id<Beverage>) beverage;
@end
//Milk.m
#import "Milk.h"
@implementation Milk{
NSString *_name;
}
- (instancetype)initWithBeverage:(id<Beverage>)beverage{
if (self = [super init]) {
_name = @"Milk";
self.beverage = beverage;
}
return self;
}
- (NSString *)getName{
return [NSString stringWithFormat:@"%@ + %@", [self.beverage getName], _name ];
}
- (double)cost{
return .30 + [self.beverage cost];
}
@end
Mocha 类
#import <Foundation/Foundation.h>
#import "Beverage.h"
#import "CondimentDecorator.h"
@interface Mocha : NSObject<CondimentDecorator>
@property (strong, nonatomic)id<Beverage> beverage;
- (instancetype)initWithBeverage:(id<Beverage>) beverage;
@end
//Mocha.m
#import "Mocha.h"
@implementation Mocha{
NSString *_name;
}
- (instancetype)initWithBeverage:(id<Beverage>)beverage{
if (self = [super init]) {
self.beverage = beverage;
_name = @"Mocha";
}
return self;
}
- (NSString *)getName{
return [NSString stringWithFormat:@"%@ + %@", [self.beverage getName], _name];
}
- (double)cost{
return .20 + [self.beverage cost];
}
@end
整合调用
main.m
#import <Foundation/Foundation.h>
#import "Espresso.h"
#import "DarkRoast.h"
#import "Milk.h"
#import "Mocha.h"
#import "Soy.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
id<Beverage> espresso = [[Espresso alloc]init];
NSLog(@"name: %@ \n cost: %f \n", [espresso getName], [espresso cost]);
espresso = [[Milk alloc]initWithBeverage:espresso];
espresso = [[Mocha alloc]initWithBeverage:espresso];
NSLog(@"name: %@ \n cost:%f", [espresso getName], [espresso cost]);
}
return 0;
}
优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”
缺点
使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。