设计模式-装饰者模式

998 阅读5分钟

要解决的问题

考虑一个咖啡店收费的问题:如何实现灵活的咖啡的价格计算。 咖啡店主要卖咖啡,但是后来为了满足不同客户的不同口味,只是纯咖啡,显得太单调了,就考虑增加不同的搭配,搭配不同的配料后会组成另一种饮品,这样,品种丰富了,但随之问题也来了,如何为不同新的品种计算新的准确的价格。两种方案:

第一种:可以采用继承的方式,将纯咖啡作为基类,而后需要什么品种的话,可以生成一个子类,单独作为一个品种来重写计算价格的方法,并且还可以为这个品种添加其他功能。但是:继承有一个很大的问题就是,这样的方案实现是首先你是知道都有什么品种的,才会派生出各种子类,但是,如果后续想要在某个现有品种中去掉一些或者加上一些内容,甚至直接删除这个品种,就会很麻烦的总是修改对应的类了,而且还有个缺点是:会产生很多种子类,如果品种很多,而且每个品种的差别很小的时候,都分别单独的作为一个类就会很麻烦。

第二种:就是采用我们今天要讲的这篇文章的主题装饰者模式。先类比一下生活中的一个例子:一张纸质的照片,想要让这张照片保存的久一点,我们可以先给这张照片塑封;塑封后,觉得还不够的话,可能还会给这张照片装一个相框;加一个相框还觉得不能好好保护相片的话,再加个玻璃罩。在这个例子中,我们可以理解照片本身就是要被装饰的对象,塑封胶、相框、玻璃罩都是作为装饰者。每一层的装饰者都不会修改最里边的被装饰的对象。这里我们可以把具体的咖啡饮品当做被装饰者,要加入的食物或饮料当做装饰者,每一种咖啡饮品可以被不同的装饰者装饰。

将上述为不同咖啡饮品计算价格的问题用编程的概念来讲就是如何能够透明地给一个对象增加功能,并实现功能的动态组合。这就是装饰者模式的功能。

模式定义

装饰者模式能够实现动态地为对象添加功能,从一个对象外部透明的给对象增加功能。透明地给一个对象增加功能,就是说要给一个对象增加功能,但是不能让这个对象知道,也就是不能去修改这个对象

每个被装饰者可以被多个装饰者装饰。例如:黑咖啡(被装饰者)可以被牛奶(装饰者)、水果(装饰者)装饰,而且,不同的装饰者之间没有先后顺序的限制。

具体实现

装饰者需要和被装饰的对象继承于同样的类或者实现同样的接口(iOS中称遵守同样的协议),而后,在具体的装饰者的实现中,转调被装饰者对象【这就需要装饰者对象持有一个被装饰者对象】

下边是具体实现的UML图和不同类之间的调用层次图。

  • CoffeeComponent:咖啡基类(也可以是接口/协议)
  • BlackCoffee:具体的咖啡,就是要被装饰的对象。
  • CondimentDecorator:配料的基类(装饰者的基类),而且需要继承于被装饰者基类CoffeeComponent,同时还要持有一个CoffeeComponent类型的属性。
  • MilkDecorator:牛奶装饰者,具体的装饰者对象。

从上边的层次图中可以看出,多层装饰者一层层的包在被装饰对象的外部,功能方法的调用也是一层层递归调用被装饰的对象。从图中可以看出:当黑咖啡被牛奶装饰后,牛奶装饰器就成为了新的被装饰者,可以被后续的其它装饰者装饰,而且各个装饰者之间是没有顺序要求的。顺序完全可以按照自己的意愿来进行。

//********************************咖啡组件(基类)*********************
@interface CoffeeComponent : NSObject
- (double)getPrice;
@end

@implementation CoffeeComponent//抽象组件,可以写默认实现的方法,也可以用协议实现
- (double)getPrice
{
    return 0.f;
}
@end

//********************************黑咖啡(具体咖啡类)*********************
@interface BlackCoffee : CoffeeComponent//继承于抽象组件的具体组件
- (double)getPrice;
@end

@implementation BlackCoffee
- (double)getPrice
{
    return 5;
}
@end

//********************************装饰者基类*********************
@interface CondimentDecorator : CoffeeComponent//继承于组件的装饰者抽象类
- (instancetype)initWithComponent:(CoffeeComponent *)component;
@property (nonatomic,strong)CoffeeComponent *component;
@end

@implementation CondimentDecorator
- (instancetype)initWithComponent:(CoffeeComponent *)component
{
    if (self = [super init]) {
        _component = component;
    }
    return self;
}
@end
//********************************牛奶装饰者(具体装饰者)*********************
@interface MilkDecorator : CondimentDecorator//继承于抽象佐料装饰者的具体装饰者
@end

@implementation MilkDecorator
- (double)getPrice
{
    NSLog(@"牛奶加了2元");
    return 2 + [self.component getPrice];
}
@end
//在此省略其它装饰者的代码,与牛奶装饰者的代码一样。代码可以查看demo。
//=========================外部调用=====================
    //纯咖啡
    BlackCoffee *blackCoffee = [[BlackCoffee alloc]init];
    //加奶
    MilkDecorator *milkDecorator = [[MilkDecorator alloc]initWithComponent:blackCoffee];
    //加豆浆
    SoyDecorator *soyDecorator = [[SoyDecorator alloc]initWithComponent:milkDecorator];
    //加水果
    FruitDecorator *fruitDecorator = [[FruitDecorator alloc]initWithComponent:soyDecorator];
    NSLog(@"一共多少钱%f",[fruitDecorator getPrice]);

总结

  • 装饰者模式比继承更灵活:继承是静态的,而且继承的子类都有基类同样的功能,但是装饰者模式能够把功能分离到不同的装饰器中,可以动态的选择想要什么功能。
  • 装饰者模式的本质是动态组合:动态的进行装饰器的组合,可以为被装饰对象透明的增加功能。
  • 装饰者模式不仅可以增加功能,也可以完全实现新的功能和控制功能的访问。可以在装饰器中调用被装饰对象功能的时候,进行控制。
  • 装饰者模式的缺点:会产生细粒度的对象,如果一系列的复杂功能,想要把不同的功能都细分到不同的装饰器上,就会产生很多细粒度的对象。

以上作为笔者自己的读书笔记,如有理解错误的地方,还请指出。谢谢!

Demo地址


参考致谢:

《研磨设计模式》

《Head First设计模式》