前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面向对象设计的设计模式(十一):装饰者模式

面向对象设计的设计模式(十一):装饰者模式

作者头像
用户2932962
发布2019-04-25 15:13:51
4090
发布2019-04-25 15:13:51
举报
文章被收录于专栏:程序员维他命

定义

装饰者模式(Decorator Pattern) :不改变原有对象的前提下,动态地给一个对象增加一些额外的功能。

适用场景

  • 动态地给一个对象增加职责(功能),这些职责(功能)也可以动态地被撤销。
  • 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时。

成员与类图

成员

装饰者模式一共有四个成员:

  1. 抽象构件(Component):抽象构件定义一个对象(接口),可以动态地给这些对象添加职责。
  2. 具体构件(Concrete Component):具体构件是抽象构件的实例。
  3. 装饰(Decorator):装饰类也继承于抽象构件,它持有一个具体构件对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰(Concrete Decorator):具体装饰负责给具体构建对象实例添加上附加的责任。

模式类图

装饰者模式类图

代码示例

场景概述

模拟沙拉的制作:沙拉由沙拉底和酱汁两个部分组成,不同的沙拉底和酱汁搭配可以组成不同的沙拉。

沙拉底

价格

蔬菜

5

鸡肉

10

牛肉

16

酱汁

价格

醋汁

2

花生酱

4

蓝莓酱

6

注意:同一份沙拉底可以搭配多钟酱汁,而且酱汁的份数也可以不止一份。

场景分析

因为选择一个沙拉底之后,可以随意添加不同份数和种类的酱汁,也就是在原有的沙拉对象增加新的对象,所以比较适合用装饰者模式来设计:酱汁相当于装饰者,而沙拉底则是被装饰的构件。

下面我们用代码看一下如何实现该场景。

代码实现

首先我们定义 抽象构件,也就是沙拉类的基类Salad

代码语言:javascript
复制
//================== Salad.h ==================

@interface Salad : NSObject

- (NSString *)getDescription;

- (double)price;

@end

getDescriptionprice方法用来描述当前沙拉的配置以及价格(因为随着装饰者的装饰,这两个数据会一直变化)。

下面我们再声明装饰者的基类SauceDecorator。按照装饰者设计模式类图,该类是继承于沙拉类的:

代码语言:javascript
复制
//================== SauceDecorator.h ==================

@interface SauceDecorator : Salad

@property (nonatomic, strong) Salad *salad;

- (instancetype)initWithSalad:(Salad *)salad;

@end



//================== SauceDecorator.m ==================

@implementation SauceDecorator

- (instancetype)initWithSalad:(Salad *)salad{

    self = [super init];

    if (self) {
        self.salad = salad;
    }
    return self;
}

@end

在装饰者的构造方法里面传入Salad类的实例,并将它保存下来,目的是为了在装饰它的时候用到。

现在抽象构件和装饰者的基类都创建好了,下面我们创建具体构件和具体装饰者。首先我们创建具体构件:

  • 蔬菜沙拉
  • 鸡肉沙拉
  • 牛肉沙拉

蔬菜沙拉VegetableSalad

代码语言:javascript
复制
//================== VegetableSalad.h ==================

@interface VegetableSalad : Salad

@end



//================== VegetableSalad.m ==================

@implementation VegetableSalad

- (NSString *)getDescription{
    return @"[Vegetable Salad]";
}

- (double)price{
    return 5.0;
}

@end

首先getDescription方法返回的是蔬菜沙拉底的描述;然后price方法返回的是它所对应的价格。

类似的,我们继续按照价格表来创建鸡肉沙拉底和牛肉沙拉底:

鸡肉沙拉底:

代码语言:javascript
复制
//================== ChickenSalad.h ==================

@interface ChickenSalad : Salad

@end



//================== ChickenSalad.m ==================
@implementation ChickenSalad

- (NSString *)getDescription{
    return @"[Chicken Salad]";
}

- (double)price{
    return 10.0;
}

@end

牛肉沙拉底:

代码语言:javascript
复制
//================== BeefSalad.h ==================

@interface BeefSalad : Salad

@end



//================== BeefSalad.m ==================

@implementation BeefSalad


- (NSString *)getDescription{
    return @"[Beef Salad]";
}

- (double)price{
    return 16.0;
}

@end

现在所有的被装饰者创建好了,下面我们按照酱汁的价格表来创建酱汁类(也就是具体装饰者):

  • 醋汁
  • 花生酱
  • 蓝莓酱

首先看一下醋汁VinegarSauceDecorator:

代码语言:javascript
复制
//================== VinegarSauceDecorator.h ==================

@interface VinegarSauceDecorator : SauceDecorator

@end



//================== VinegarSauceDecorator.m ==================    

@implementation VinegarSauceDecorator

- (NSString *)getDescription{
    return [NSString stringWithFormat:@"%@ + vinegar sauce",[self.salad getDescription]];
}

- (double)price{
    return [self.salad price] + 2.0;
}

@end

重写了getDescription方法,并添加了自己的装饰,即在原来的描述上增加了+ vinegar sauce字符串。之所以可以获取到原有的描述,是因为在构造方法里已经获取了被装饰者的对象(在装饰者基类中定义的方法)。同样地,价格也在原来的基础上增加了自己的价格。

现在我们知道了具体装饰者的设计,以此类推,我们看一下花生酱和蓝莓酱类如何定义:

花生酱PeanutButterSauceDecorator类:

代码语言:javascript
复制
//================== PeanutButterSauceDecorator.h ==================     

@interface PeanutButterSauceDecorator : SauceDecorator

@end



//================== PeanutButterSauceDecorator.m ==================     
@implementation PeanutButterSauceDecorator

- (NSString *)getDescription{
    return [NSString stringWithFormat:@"%@ + peanut butter sauce",[self.salad getDescription]];
}

- (double)price{
    return [self.salad price] + 4.0;
}

@end

蓝莓酱类BlueBerrySauceDecorator:

代码语言:javascript
复制
//================== BlueBerrySauceDecorator.h ==================     

@interface BlueBerrySauceDecorator : SauceDecorator

@end



//================== BlueBerrySauceDecorator.m ==================     

@implementation BlueBerrySauceDecorator

- (NSString *)getDescription{

    return [NSString stringWithFormat:@"%@ + blueberry sauce",[self.salad getDescription]];
}

- (double)price{

    return [self.salad price] + 6.0;
}

@end

OK,到现在所有的类已经定义好了,为了验证是否实现正确,下面用客户端尝试着搭配几种不同的沙拉吧:

  1. 蔬菜加单份醋汁沙拉(7元)
  2. 牛肉加双份花生酱沙拉(24元)
  3. 鸡肉加单份花生酱再加单份蓝莓酱沙拉(20元)

首先我们看第一个搭配:

代码语言:javascript
复制
//================== client ==================     

//vegetable salad add vinegar sauce
Salad *vegetableSalad = [[VegetableSalad alloc] init];
NSLog(@"%@",vegetableSalad);

vegetableSalad = [[VinegarSauceDecorator alloc] initWithSalad:vegetableSalad];
NSLog(@"%@",vegetableSalad);

第一次打印输出:This salad is: [Vegetable Salad] and the price is: 5.00 第二次打印输出:This salad is: [Vegetable Salad] + vinegar sauce and the price is: 7.00

上面代码中,我们首先创建了蔬菜底,然后再让醋汁装饰它(将蔬菜底的实例传入醋汁装饰者的构造方法中)。最后我们打印这个蔬菜底对象,描述和价格和装饰之前的确实发生了变化,说明我们的代码没有问题。

接着我们看第二个搭配:

代码语言:javascript
复制
//================== client ================== 

//beef salad add two peanut butter sauce:
Salad *beefSalad = [[BeefSalad alloc] init];
NSLog(@"%@",beefSalad);

beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);

beefSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:beefSalad];
NSLog(@"%@",beefSalad);

第一次打印输出:[Beef Salad] and the price is: 16.00 第二次打印输出:[Beef Salad] + peanut butter sauce and the price is: 20.00 第三次打印输出:[Beef Salad] + peanut butter sauce + peanut butter sauce and the price is: 24.00

和上面的代码实现类似,都是先创建沙拉底(这次是牛肉底),然后再添加调料。由于是分两次装饰,所以要再写一次花生酱的装饰代码。对比每次打印的结果和上面的价格表可以看出输出是正确的。

这个例子是加了两次相同的酱汁,最后我们看第三个搭配,加入的是不同的两个酱汁:

代码语言:javascript
复制
//================== client ================== 

//chiken salad add peanut butter sauce and blueberry sauce
Salad *chikenSalad = [[ChickenSalad alloc] init];
NSLog(@"%@",chikenSalad);

chikenSalad = [[PeanutButterSauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);

chikenSalad = [[BlueBerrySauceDecorator alloc] initWithSalad:chikenSalad];
NSLog(@"%@",chikenSalad);

第一次打印输出:[Chicken Salad] and the price is: 10.00 第二次打印输出:[Chicken Salad] + peanut butter sauce and the price is: 14.00 第三次打印输出:[Chicken Salad] + peanut butter sauce + blueberry sauce and the price is: 20.00

对比每次打印的结果和上面的价格表可以看出输出是正确的。

到这里,该场景就模拟结束了。可以试想一下,如果今后加了其他的沙拉底和酱汁的话,只需要分别继承Salad类和SauceDecorator类就可以了,现有的代码并不需要更改;而且经过不同组合可以搭配出更多种类的沙拉。

下面我们看一下该代码实现对应的类图。

代码对应的类图

装饰者模式代码示例类图

优点

  • 比继承更加灵活:不同于在编译期起作用的继承;装饰者模式可以在运行时扩展一个对象的功能。另外也可以通过配置文件在运行时选择不同的装饰器,从而实现不同的行为。也可以通过不同的组合,可以实现不同效果。
  • 符合“开闭原则”:装饰者和被装饰者可以独立变化。用户可以根据需要增加新的装饰类,在使用时再对其进行组合,原有代码无须改变。

缺点

  • 装饰者模式需要创建一些具体装饰类,会增加系统的复杂度。

Objective-C & Java的实践

  • Objective-C中暂时未发现装饰者模式的实践,有知道的小伙伴可以留言
  • JDK中:BufferReader继承了Reader,在BufferReader的构造器中传入了Reader,实现了装饰
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员维他命 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
    • 适用场景
      • 成员与类图
        • 成员
        • 模式类图
      • 代码示例
        • 场景概述
        • 场景分析
        • 代码实现
        • 代码对应的类图
      • 优点
        • 缺点
          • Objective-C & Java的实践
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档