大部分公司都有销售团队,假设老板给你布置了一个任务,让你按照下面的要求开发一套程序来计算销售团队每个月的工资。
- 每个人当月业务奖金 = 当月销售额 * 3%
- 每个人的累积奖金 = 总的回款额 * 0.1%
- 销售经理的团队奖金 = 团队总销售额 * 1%
每个人的工资就是基本工资加上奖金,那么按照常规模式我们来看下如何让实现。
#import "calculateBonus.h"
@implementation calculateBonus
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales isManager:(BOOL)manager{
//基本工资都是8000
NSInteger salary = 8000;
salary += [self monthBonus:monthSales];
salary += [self sumBonus:sumSales];
if (manager) {
salary += [self groupBonus];
}
return salary;
}
//当月奖金
-(NSInteger)monthBonus:(NSInteger)monthSales{
return monthSales * 0.003;
}
//累积奖金
-(NSInteger)sumBonus:(NSInteger)sumSales{
return sumSales * 0.001;
}
//团队奖金
-(NSInteger)groupBonus{
//简单起见,团队的销售总额设置为100000
return 100000 * 0.01;
}
@end复制代码
测试下:
calculateBonus *calculate = [calculateBonus new];
NSInteger salary1 = [calculate calculateSalary:12222 sumSales:12000 isManager:YES];
NSLog(@"经理工资:%zd", salary1);
NSInteger salary2 = [calculate calculateSalary:21333 sumSales:23111 isManager:NO];
NSLog(@"员工甲:%zd", salary2);
NSInteger salary3 = [calculate calculateSalary:22113 sumSales:11222 isManager:NO];
NSLog(@"员工乙:%zd", salary3);复制代码
输出:
2016-12-14 08:57:58.600 装饰者模式[64313:1880733] 经理工资:9048
2016-12-14 08:57:58.601 装饰者模式[64313:1880733] 员工甲:8086
2016-12-14 08:57:58.601 装饰者模式[64313:1880733] 员工乙:8077
Program ended with exit code: 0复制代码
看起来运行良好,好,该来的还是来了,该需求。
现在要增加一个环比奖金:就是本月销售额比上月又增加,然后达到一定比例,就会有奖金,增加越多,奖金比率越高。你说这还不简单,再加一个环比奖金计算方法不就是了。那么如果工资计算的方式也换了呢?不同级别的人员或者员工的计算奖金的方式也换了呢?
假设
甲的总工资 = 基本工资 + 当月销售奖金 + 环比奖金
乙的工资 = 基本工资 + 环比奖金
丙的工资 = 基本工资 + 当月销售奖金
丁的工资 = 基本工资 + 环比奖金 + 团队奖金
后面再给你制定十几种不同的计算方式,崩溃了没有?按照上面的写法,那么每一种总工资计算方式你都要写一种方法,再假设这些人的总工资计算方式每个季度还会有调整,你怎么办,接着改?
仔细分析下上面的需求,总工资的计算有两部分:基本工资加上各种奖金,基本工资是固定的,麻烦的地方就在于奖金的计算方式是动态变化的,有各种各样的组合方式。按照设计模式的思想:封装变化,这里变化的部分是奖金的组合方式,那么我们就把这部分封装起来。
我们可以采取这样的方式,把每张奖金的计算方式都单独成类,然后需要用到哪种奖金计算,就把这个奖金计算和基本工资组合起来,需要多少种奖金计算方式,就组合多少种。这样实现起来,是不是非常灵活?以后你想修改或者增加减少奖金计算方式,只需要修改或者增加减少一个奖金计算方式就可以了,至于每个人的总工资计算方式各不相同,就更简单了,交给客户端自由组合。
总结起来就是如下三个要求:
奖金计算方式要灵活,可以动态增加或者减少
可以动态组合奖金计算方式
要实现上面的功能,就要清楚我们今天的猪脚:装饰器模式。
下面来认识下这位仁兄
定义
动态地给一个对象添加一些额外的职责。就增加功能来说, 装饰模式比生成子类更为灵活。
一般我们在给一个原本的类添加功能的时候,都会想到使用继承,在原有的类上扩展新的功能,但是继承有一个非常大的缺点:和父类耦合性太高。如果后续需要添加或者减少功能,就不得不每次都要修改子类,而且如果修改了父类,对子类的影响也非常大。
所以我们一般优先考虑使用组合来实现功能的扩展,这也是设计模式的一个原则:多用组合,少用继承。装饰器模式就是实现组合功能的一种方式,它可以透明的给原本的类增加或者减少功能,而且可以把多个功能组合在一起,他不会改变原有类的功能,只是在原来功能的基础上加上一些新功能,而这些操作被装饰对象是毫不知情的。
比如上面的计算总工资,原有的对象是基本工资,但是需要在基本工资的基础上加上各种奖金,也就是给基本工资扩展了功能,但是基本工资这个原有功能是不会改变的,只是给他加上了各种各样的奖金,丰富了它的功能,最后算出来的还是工资,也就是保持原有的类型(整数型)不改变,这点要切记。
UML结构如及说明
实现装饰器模式要注意如下几点:
1.接口的一致性
装饰对象的接口必须与它所装饰的Component的接口是一致的,因此,所有的concreteDecorator类必须有一个公共的父类
2.省略抽象的Decorator类
当你仅需要添加一个职责时,没有必要定义抽象Decorator类。你常常需要处理现存的类层次结构而不是设计一个新系统,这时你可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。
3.保持Component类的简单性
为了保证接口的一致性,组件和装饰必须有一个公共的Component父类。因此保持这个类的简单性是很重要的;即,它应集中于定义接口而不是存储数据。对数据表示的定义应延迟到子类中,否则Component类会变得过于复杂和庞大,因而难以大量使用。赋予Component太多的功能也使得,具体的子类有一些它们并不需要的功能的可能性大大增加。
4.改变对象外壳与改变对象内核
我们可以将Decorator看作一个对象的外壳,它可以改变这个对象的行为。另外一种方法是改变对象的内核。例如,Strategy模式就是一个用于改变内核的很好的模式。
当Component类原本就很庞大时,使用Decorator模式代价太高,Strategy模式相对更好一些。在Strategy模式中,组件将它的一些行为转发给一个独立的策略对象,我们可以替换strategy对象,从而改变或扩充组件的功能。
代码实现
1、定义抽象基类
先定义一个抽象基类,工资类和奖金计算方式类都继承自这个类,该类定义了一个公开接口,用于计算奖金
#import <Foundation/Foundation.h>
@interface component : NSObject
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales;
@end
=================复制代码
2、定义工资类(被装饰对象)
#import "component.h"
@interface concreteComponent : component
@end
======================
//被装饰对象,基本工资
#import "concreteComponent.h"
@implementation concreteComponent
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
//基本工资8000
return 8000;
}
@end复制代码
3、定义抽象装饰器
定义一个抽象装饰器,继承自抽象基类component,每个具体的装饰器继承自该类,该类主要做一些初始化工作
#import "component.h"
@interface Decorator : component
@property(strong,nonatomic)component *components;
- (instancetype)initWithComponet:(component *)component;
@end
=================
#import "Decorator.h"
@implementation Decorator
- (instancetype)initWithComponet:(component *)component
{
self = [super init];
if (self) {
self.components = component;
}
return self;
}
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
return [self.components calculateSalary:monthSales sumSales:sumSales];
}
@end复制代码
4、具体装饰器
每月销售奖金装饰器
#import "Decorator.h"
@interface monthBonusDecorator : Decorator
@end
==================
#import "monthBonusDecorator.h"
@implementation monthBonusDecorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = monthSales * 0.03;
NSLog(@"当月销售奖金:%zd", bonus);
return salary += bonus;
}
@end复制代码
累积奖金装饰器
#import "Decorator.h"
@interface sumBonusDecatorator : Decorator
@end
================
#import "sumBonusDecatorator.h"
@implementation sumBonusDecatorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = sumSales * 0.01;
NSLog(@"累积销售奖金:%zd", bonus);
return salary += bonus;
}
@end复制代码
团队奖金装饰器
#import "Decorator.h"
@interface groupBonusDecorator : Decorator
@end
=================
#import "groupBonusDecorator.h"
@implementation groupBonusDecorator
-(NSInteger)calculateSalary:(NSInteger)monthSales sumSales:(NSInteger)sumSales{
NSInteger salary = [self.components calculateSalary:monthSales sumSales:sumSales];
NSInteger bonus = 100000 * 0.01;
NSLog(@"团队奖金:%zd", bonus);
return salary += bonus;
}
@end复制代码
5、测试
//基本工资,被装饰对象
component *c1 = [concreteComponent new];
//装饰器
Decorator *d1 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d2 = [[sumBonusDecatorator alloc]initWithComponet:d1];
NSInteger salary1 = [d2 calculateSalary:10000 sumSales:12212];
NSLog(@"\n奖金组合方式:当月销售奖金 + 累积销售奖金 \n 总工资 = %zd", salary1);
NSLog(@"\n=============================================================================");
Decorator *d3 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d4 = [[sumBonusDecatorator alloc]initWithComponet:d3];
Decorator *d5 = [[groupBonusDecorator alloc]initWithComponet:d4];
NSInteger salary2 = [d5 calculateSalary:12100 sumSales:12232];
NSLog(@"\n奖金组合方式:当月销售奖金 + 累积销售奖金 + 团队奖金 \n 总工资 = %zd", salary2);
NSLog(@"\n=============================================================================");
Decorator *d6 = [[monthBonusDecorator alloc]initWithComponet:c1];
Decorator *d7 = [[groupBonusDecorator alloc]initWithComponet:d6];
NSInteger salary3 = [d7 calculateSalary:23111 sumSales:231111];
NSLog(@"\n奖金组合方式:当月销售奖金 + 团队奖金 \n 总工资 = %zd", salary3);复制代码
输出如下
2016-12-14 10:34:31.280 装饰者模式[64586:1944336] 当月销售奖金:300
2016-12-14 10:34:31.280 装饰者模式[64586:1944336] 累积销售奖金:122
2016-12-14 10:34:31.280 装饰者模式[64586:1944336]
奖金组合方式:当月销售奖金 + 累积销售奖金
总工资 = 8422
=============================================================================
2016-12-14 10:34:31.280 装饰者模式[64586:1944336] 当月销售奖金:363
2016-12-14 10:34:31.280 装饰者模式[64586:1944336] 累积销售奖金:122
2016-12-14 10:34:31.280 装饰者模式[64586:1944336] 团队奖金:1000
2016-12-14 10:34:31.280 装饰者模式[64586:1944336]
奖金组合方式:当月销售奖金 + 累积销售奖金 + 团队奖金
总工资 = 9485
=============================================================================
2016-12-14 10:34:31.281 装饰者模式[64586:1944336] 当月销售奖金:693
2016-12-14 10:34:31.281 装饰者模式[64586:1944336] 团队奖金:1000
2016-12-14 10:34:31.281 装饰者模式[64586:1944336]
奖金组合方式:当月销售奖金 + 团队奖金
总工资 = 9693复制代码
6、小结
从上面的测试可以看出,不管是使用何种奖金组合方式,只需要调用对应的装饰器即可,非常灵活。通过上面的代码我们看到,装饰器是一层层包裹的,基本工资被月工资装饰器包裹,月工资装饰器被累积奖金装饰器包裹,累积装饰器被团队奖金装饰器包裹,当调用计算奖金的公式的时候,就会按照顺序层层递归调用每个装饰器的功能,到最后算出总工资,我们来用示意图看看调用过程。
由于每个装饰器之间是完全独立的,所以我们可以使用任何我们想要的方式去组合这些装饰器,比如多次重复调用同一个装饰器,调换装饰器的顺序等等。
适用性
在如下情况可以考虑使用对象组合
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
处理那些可以撤消的职责。
当不能采用生成子类的方法进行扩充时
一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
优缺点
比继 承 更 灵 活
与 对 象 的 静 态 继 承 ( 多 重 继 承 ) 相 比 , D e c o r a t o r 模 式 提 供 了 更 加
灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职 责。相比之下,继承机制要求为每个添加的职责创建一个新的子类。这会产生许多新的类,并且会增加系统的复杂度。此外,为一 个特定的 C o m p o n e n t 类 提 供 多 个 不 同 的 D e c o r a t o r 类 , 这 就 使 得 你 可 以 对 一 些 职 责 进 行 混 合 和 匹配。
使用 D e c o r a t o r 模式可以很容易地重复添加一个特性。避 免 了高层次类 有 太 多 的 特 征
D e c o r a t o r模 式 提 供 了 一 种 “ 即 用 即 付 ” 的 方 法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可 以定义一个简单的类,并且用 D e c o r a t o r 类 给 它 逐 渐 地 添 加 功 能 。 可 以 从 简 单 的 部 件 组 合 出 复 杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于 D e c o r a t o r 所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的 D e c o r a t o r 。 扩 展 一 个 复 杂 类 的 时候,很可能会暴露与添加的职责无关的细节。
产生 许 多 小 对 象
采用 D e c o r a t o r 模 式 进 行 系 统 设 计 往 往 会 产 生 许 多 看 上 去 类 似 的 小 对 象 , 这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不 同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统, 排错也很困难。
装饰器和AOP
关于面向切换编程的具体解释看这里
AOP一般用来实现如下功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
看上面的描述就知道,装饰器就是一个很好实现AOP的方式,因为装饰器就是在不改变原有功能的前提下对其进行透明扩展的。
我们目前有一个鸭子类,实现呱呱叫的一个方法,现在我希望在不改变原有功能的情况下,统计鸭子叫了多少次,这就是AOP中的日志记录功能,我们可以使用装饰器模式来实现,具体代码我就不贴了,直接看最后的demo。
装饰器在iOS中的运用
我们应该用过一些图片处理app,可以给图片加上各种各样的滤镜或者裁剪旋转图片等等功能,其实这些也可以使用装饰器来实现,可以把每个功能都实现为一个装饰器,然后用户选择使用什么功能,就给图片加上对应的装饰器去做处理,这样做是不是非常灵活?
其实在iOS里面已经为我们提供了类似装饰器模式的功能的方法:category。category也可以透明的为一个类添加方法,下面我就使用一个小demo来演示如何使用category和装饰者模式分别来实现图片的选择和阴影效果。具体见demo。