iOS设计模式-装饰器模式

1. 什么是装饰器设计模式

装饰模式(Decorator Pattern 定义

Attach additionalresponsibilities to an object dynamically keeping the same interface.Decorators provide aflexible alternative to subclassing for extending functionality.(动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。)

是指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式

2. 角色组成

装饰模式主要包含以下角色:

  • 抽象构件(Component):定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(ConcreteComponent):实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator):继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

UML关系图:


装饰器模式

3. 完全透明与半透明模式

  • 完全透明模式:装饰模式对客户端的透明性要求程序不要声明给一个ConcreteComponent类型的变量,而应当声明一个Component类型的变量。也就是说客户端在调用时,只能与Componnet实例来调用代码,这就要求ConcreteDecorator的接口与Component保持一致,不去新增接口,只对原有的接口进行增强。
  • 半透明模式:完全透明模式是比较少见的,因为在增强功能的过程中,往往会新增接口。这就导致客户端调用新接口的时候,必须声明ConcreteDecorator类型的变量,这样才能调用新增的接口。这种有新增功能的情况称为半透明模式,也称作半装饰、半适配器模式。

4. 代码示例

我们一英雄联盟英雄技能为例。英雄联盟作为一款竞技网游,每个英雄(Hero)本身具有一个被动技能buff,并且可以通过击杀特定的中立野怪而获取到对应的buff在一定时间内增加自身的属性。为了实现被加持不同buff的英雄,有哪些方式呢?

  1. 不改变抽象类的情况下,最容易想到的方式,自然是采取继承的方式。但是如果为每个英雄加持不同buff都新建一个类的话,这样无疑会照成类爆炸性增长,毕竟英雄数现在就有上百个了,未来还一直在增加。
  2. 使用装饰器模式为其动态的新增或者移除buff。
    无疑在这种场景下,选择装饰器模式去实现是更加合理的。接下来对其进行代码实现。

先画出UML类图:


先定义抽象构件类Hero,通过方法blessBuff展示英雄此时所加持的buff

// Hero.h
#import <Foundation/Foundation.h>

@interface Hero : NSObject
- (void)blessBuff;
@end

// Hero.m
#import "Hero.h"

@implementation Hero

- (void)blessBuff {
    NSAssert(false, @"must implement in subClass");
}

@end

定义英雄盖伦Galen和Timo

// Galen.h
#import "Hero.h"

@interface Galen : Hero

@end

// Galen.m
#import "Galen.h"

@implementation Galen
- (void)blessBuff {
    NSLog(@"盖伦被动技能:脱离战斗后回血加快");
}
@end

// Timo.h
#import "Hero.h"

@interface Timo : Hero

@end

// Timo.m
#import "Timo.h"

@implementation Timo
- (void)blessBuff {
    NSLog(@"提莫被动技能:脱离战斗后,静止不动一段时间进入隐身");
}
@end

定义buff抽象装饰器BuffDecorator

#import "Hero.h"

// BuffDecorator.h
@interface BuffDecorator : Hero
- (instancetype)initWithHero:(Hero *)hero;
@end

//BuffDecorator.m
#import "BuffDecorator.h"

@interface BuffDecorator()
@property (nonatomic, strong) Hero *hero;
@end

@implementation BuffDecorator

- (instancetype)initWithHero:(Hero *)hero {
    self = [super init];
    if (self) {
        _hero = hero;
    }
    return self;
}

- (void)blessBuff {
    [_hero blessBuff];
    NSLog(@"额外buff:");
}

@end

先来个红buff装饰器

// RedBuffDecorator.h
#import "BuffDecorator.h"

@interface RedBuffDecorator : BuffDecorator

@end

// RedBuffDecorator.m
#import "RedBuffDecorator.h"

@implementation RedBuffDecorator

- (void)blessBuff {
    [super blessBuff];
    NSLog(@"红buff: 攻击附加真实伤害,并造成灼烧效果");
}
@end

再来个蓝buff装饰器

// BlueBuffDecorator.h
#import "BuffDecorator.h"

@interface BlueBuffDecorator : BuffDecorator

@end

// BlueBuffDecorator.m
#import "BlueBuffDecorator.h"

@implementation BlueBuffDecorator
- (void)blessBuff {
    [super blessBuff];
    NSLog(@"蓝buff: 蓝量回复速度加快,并且缩减技能CD");
}
@end

开始调用,全透明模式下,直接声明变量类型为抽象构件类,Hero即可,然后依次使用不同装饰器为其增加不同buff

+ (void)test {
    NSLog(@"----------------Galen----------------------");
    Hero *galen = [Galen new];
    galen = [[RedBuffDecorator alloc] initWithHero:galen];
    [galen blessBuff];
    galen = [[BlueBuffDecorator alloc] initWithHero:galen];
    [galen blessBuff];
    NSLog(@"----------------Timo----------------------");
    Hero *timo = [Timo new];
    timo = [[RedBuffDecorator alloc] initWithHero:timo];
    [timo blessBuff];
    timo = [[BlueBuffDecorator alloc] initWithHero:timo];
    [timo blessBuff];
}

运行结果


5. 分析

  • 采用装饰器模式可以动态的扩展一个实现类的功能
  • 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
  • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。
  • 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂

6. 适用场景

  • 可以作为替代继承的一种方式
  • 动态地为对象拓展或撤销功能,但是又不希望对整个类进行拓展或撤销相应功能的情况
  • 为诸多兄弟类拓展功能时

7. 模式拓展

装饰器模式在使用的时候并不一定所有的角色都要有,一些特定情况可以省略一些角色。

  1. 当只有一个具体构件(ConcreteComponent)时,可以省略抽象构件(Component),UML类图如下:


  2. 当只有一个具体装饰类(ConcreteDecorator)时,可以把抽象装饰类和具体抽象类进行合并.UML类图如下:


8. 装饰器模式和代理模式的区别

装饰器模式可以看做是代理模式的一个特殊应用,他们有的代码实现看起来非常像,尤其是在装饰器模式省略了抽象装饰器后。我们来分析下他们的异同点:

相同点

  • 对装饰器模式来说,装饰者和被装饰者都实现同一个 接口。对代理模式来说,代理类和真实处理的类也都实现同一个接口。他们之间的边界确实比较模糊,两者都是对类的方法进行扩展

异同点

  • 代理模式强调对访问的限制,被代理对象往往不易直接获取,或者希望得到隐藏。装饰器模式更多的强调增强功能上,在原有的类上动态地拓展功能
  • 代理模式是把当前的行为或功能委托给其他对象执行,代理类负责接口限定:是否可以调用真实角色,以及是否对发送到真实角色的消息进行变形处理,它不对被主题角色(也就是被代理类)的功能做任何处理,保证原汁原味的调用
  • 装饰模式是在要保证接口不变的情况下加强类的功能,它保证的是被修饰的对象功能比原始对象丰富(当然,也可以减弱),但不做准入条件判断和准入参数过滤,如是否可以执行类的功能,过滤输入参数是否合规等
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358