用来解决上述问题的一个合理的解决方案,就是使用装饰模式。那么什么是装饰模式呢?html
(1)装饰模式定义测试
(2)应用装饰模式来解决的思路this
虽然通过简化,业务简单了不少,可是须要解决的问题不会少,仍是要解决:要透明的给一个对象增长功能,并实现功能的动态组合。spa
所谓透明的给一个对象增长功能,换句话说就是要给一个对象增长功能,可是不能让这个对象知道,也就是不能去改动这个对象。而实现了可以给一个对象透明的增长功能,天然就可以实现功能的动态组合,好比原来的对象有A功能,如今透明的给它增长了一个B功能,是否是就至关于动态组合了A和B功能呢。.net
要想实现透明的给一个对象增长功能,也就是要扩展对象的功能了,使用继承啊,有人立刻提出了一个方案,但很快就被否决了,那要减小或者修改功能呢?事实上继承是很是不灵活的复用方式。那就用“对象组合”嘛,又有人提出新的方案来了,这个方案获得了你们的赞同。component
在装饰模式的实现中,为了可以和原来使用被装饰对象的代码实现无缝结合,是经过定义一个抽象类,让这个类实现与被装饰对象相同的接口,而后在具体实现类里面,转调被装饰的对象,在转调的先后添加新的功能,这就实现了给被装饰对象增长功能,这个思路跟“对象组合”很是相似。若是对“对象组合”不熟悉,请参见22.3.1的第2小节。htm
在转调的时候,若是以为被装饰的对象的功能再也不须要了,还能够直接替换掉,也就是再也不转调,而是在装饰对象里面彻底全新的实现。对象
装饰模式的结构如图22.1所示:blog
图22.1 装饰模式结构图继承
Component:
组件对象的接口,能够给这些对象动态的添加职责。
ConcreteComponent:
具体的组件对象,实现组件对象接口,一般就是被装饰器装饰的原始对象,也就是能够给这个对象添加职责。
Decorator:
全部装饰器的抽象父类,须要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
注意这个被装饰的对象不必定是最原始的那个对象了,也多是被其它装饰器装饰事后的对象,反正都是实现的同一个接口,也就是同一类型。
ConcreteDecorator:
实际的装饰器对象,实现具体要向被装饰对象添加的功能。
(1)先来看看组件对象的接口定义,示例代码以下:
/** * 组件对象的接口,能够给这些对象动态的添加职责 */ public abstract class Component { /** * 示例方法 */ public abstract void operation(); } |
(2)定义了接口,那就看看具体组件实现对象示意吧,示例代码以下:
/** * 具体实现组件对象接口的对象 */ public class ConcreteComponent extends Component { public void operation() { //相应的功能处理 } } |
(3)接下来看看抽象的装饰器对象,示例代码以下:
/** * 装饰器接口,维持一个指向组件对象的接口对象,并定义一个与组件接口一致的接口 */ public abstract class Decorator extends Component { /** * 持有组件对象 */ protected Component component; /** * 构造方法,传入组件对象 * @param component 组件对象 */ public Decorator(Component component) { this.component = component; } public void operation() { //转发请求给组件对象,能够在转发先后执行一些附加动做 component.operation(); } } |
(4)该来看看具体的装饰器实现对象了,这里有两个示意对象,一个示意了添加状态,一个示意了添加职责。先看添加了状态的示意对象吧,示例代码以下:
/** * 装饰器的具体实现对象,向组件对象添加职责 */ public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } /** * 添加的状态 */ private String addedState; public String getAddedState() { return addedState; } public void setAddedState(String addedState) { this.addedState = addedState; } public void operation() { //调用父类的方法,能够在调用先后执行一些附加动做 //在这里进行处理的时候,可使用添加的状态 super.operation(); } } |
接下来看看添加职责的示意对象,示例代码以下:
/** * 装饰器的具体实现对象,向组件对象添加职责 */ public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } /** * 须要添加的职责 */ private void addedBehavior() { //须要添加的职责实现 } public void operation() { //调用父类的方法,能够在调用先后执行一些附加动做 super.operation(); addedBehavior(); } } |
看完了装饰模式的基本知识,该来考虑如何使用装饰模式重写前面的示例了。要使用装饰模式来重写前面的示例,大体会有以下改变:
首先须要定义一个组件对象的接口,在这个接口里面定义计算奖金的业务方法,由于外部就是使用这个接口来操做装饰模式构成的对象结构中的对象
须要添加一个基本的实现组件接口的对象,可让它返回奖金为0就能够了
把各个计算奖金的规则看成装饰器对象,须要为它们定义一个统一的抽象的装饰器对象,好约束各个具体的装饰器的接口
把各个计算奖金的规则实现成为具体的装饰器对象
先看看如今示例的总体结构,好总体理解和把握示例,如图22.2所示:
图22.2 使用装饰模式重写示例的程序结构示意图
(1)计算奖金的组件接口和基本的实现对象
在计算奖金的组件接口中,须要定义本来的业务方法,也就是实现奖金计算的方法,示例代码以下:
/** * 计算奖金的组件接口 */ public abstract class Component { /** * 计算某人在某段时间内的奖金,有些参数在演示中并不会使用, * 可是在实际业务实现上是会用的,为了表示这是个具体的业务方法, * 所以这些参数被保留了 * @param user 被计算奖金的人员 * @param begin 计算奖金的开始时间 * @param end 计算奖金的结束时间 * @return 某人在某段时间内的奖金 */ public abstract double calcPrize(String user ,Date begin,Date end); } |
为这个业务接口提供一个基本的实现,示例代码以下:
/** * 基本的实现计算奖金的类,也是被装饰器装饰的对象 */ public class ConcreteComponent extends Component{ public double calcPrize(String user, Date begin, Date end) { //只是一个默认的实现,默认没有奖金 return 0; } } |
(2)定义抽象的装饰器
在进一步定义装饰器以前,先定义出各个装饰器公共的父类,在这里定义全部装饰器对象须要实现的方法。这个父类应该实现组件的接口,这样才能保证装饰后的对象仍然能够继续被装饰。示例代码以下:
/** * 装饰器的接口,须要跟被装饰的对象实现一样的接口 */ public abstract class Decorator extends Component{ /** * 持有被装饰的组件对象 */ protected Component c; /** * 经过构造方法传入被装饰的对象 * @param c被装饰的对象 */ public Decorator(Component c){ this.c = c; } public double calcPrize(String user, Date begin, Date end) { //转调组件对象的方法 return c.calcPrize(user, begin, end); } } |
(3)定义一系列的装饰器对象
用一个具体的装饰器对象,来实现一条计算奖金的规则,如今有三条计算奖金的规则,那就对应有三个装饰器对象来实现,依次来看看它们的实现。
这些装饰器涉及到的TempDB跟之前同样,这里就不去赘述了。
首先来看实现计算当月业务奖金的装饰器,示例代码以下:
/** * 装饰器对象,计算当月业务奖金 */ public class MonthPrizeDecorator extends Decorator{ public MonthPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:而后计算当月业务奖金,按人员和时间去获取当月业务额,而后再乘以3% double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03; System.out.println(user+"当月业务奖金"+prize); return money + prize; } } |
接下来看实现计算累计奖金的装饰器,示例代码以下:
/** * 装饰器对象,计算累计奖金 */ public class SumPrizeDecorator extends Decorator{ public SumPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:而后计算累计奖金,其实应按人员去获取累计的业务额,而后再乘以0.1% //简单演示一下,假定你们的累计业务额都是1000000元 double prize = 1000000 * 0.001; System.out.println(user+"累计奖金"+prize); return money + prize; } } |
接下来看实现计算当月团队业务奖金的装饰器,示例代码以下:
/** * 装饰器对象,计算当月团队业务奖金 */ public class GroupPrizeDecorator extends Decorator{ public GroupPrizeDecorator(Component c){ super(c); } public double calcPrize(String user, Date begin, Date end) { //1:先获取前面运算出来的奖金 double money = super.calcPrize(user, begin, end); //2:而后计算当月团队业务奖金,先计算出团队总的业务额,而后再乘以1% //假设都是一个团队的 double group = 0.0; for(double d : TempDB.mapMonthSaleMoney.values()){ group += d; } double prize = group * 0.01; System.out.println(user+"当月团队业务奖金"+prize); return money + prize; } } |
(4)使用装饰器的客户端
使用装饰器的客户端,首先须要建立被装饰的对象,而后建立须要的装饰对象,接下来重要的工做就是组合装饰对象,依次对前面的对象进行装饰。
有不少相似的例子,好比生活中的装修,就拿装饰墙壁来讲吧:没有装饰前是原始的砖墙,这就比如是被装饰的对象,首先须要刷腻子,把墙找平,这就比如对原始的砖墙进行了一次装饰,而刷的腻子就比如是一个装饰器对象;好了,装饰一回了,接下来该刷墙面漆了,这又比如装饰了一回,刷的墙面漆就比如是又一个装饰器对象,并且这回被装饰的对象不是原始的砖墙了,而是被腻子装饰器装饰事后的墙面,也就是说后面的装饰器是在前面的装饰器装饰事后的基础之上,继续装饰的,相似于一层一层叠加的功能。
一样的道理,计算奖金也是这样,先建立基本的奖金对象,而后组合须要计算的奖金类型,依次组合计算,最后的结果就是总的奖金。示例代码以下:
/** * 使用装饰模式的客户端 */ public class Client { public static void main(String[] args) { //先建立计算基本奖金的类,这也是被装饰的对象 Component c1 = new ConcreteComponent();
//而后对计算的基本奖金进行装饰,这里要组合各个装饰 //说明,各个装饰者之间最好是不要有前后顺序的限制, //也就是先装饰谁和后装饰谁都应该是同样的
//先组合普通业务人员的奖金计算 Decorator d1 = new MonthPrizeDecorator(c1); Decorator d2 = new SumPrizeDecorator(d1);
//注意:这里只需使用最后组合好的对象调用业务方法便可,会依次调用回去 //日期对象都没有用上,因此传null就能够了 double zs = d2.calcPrize("张三",null,null); System.out.println("==========张三应得奖金:"+zs); double ls = d2.calcPrize("李四",null,null); System.out.println("==========李四应得奖金:"+ls);
//若是是业务经理,还须要一个计算团队的奖金计算 Decorator d3 = new GroupPrizeDecorator(d2); double ww = d3.calcPrize("王五",null,null); System.out.println("==========王经理应得奖金:"+ww); } } |
测试一下,看看结果,示例以下:
张三当月业务奖金300.0 张三累计奖金1000.0 ==========张三应得奖金:1300.0 李四当月业务奖金600.0 李四累计奖金1000.0 ==========李四应得奖金:1600.0 王五当月业务奖金900.0 王五累计奖金1000.0 王五当月团队业务奖金600.0 ==========王经理应得奖金:2500.0 |
当测试运行的时候会按照装饰器的组合顺序,依次调用相应的装饰器来执行业务功能,是一个递归的调用方法,以业务经理“王五”的奖金计算作例子,画个图来讲明奖金的计算过程吧,看看是如何调用的,如图22.3所示:
图22.3 装饰模式示例的组合和调用过程示意图
这个图很好的揭示了装饰模式的组合和调用过程,请仔细体会一下。
如同上面的示例,对于基本的计算奖金的对象而言,因为计算奖金的逻辑太过于复杂,并且须要在不一样的状况下进行不一样的运算,为了灵活性,把多种计算奖金的方式分散到不一样的装饰器对象里面,采用动态组合的方式,来给基本的计算奖金的对象增添计算奖金的功能,每一个装饰器至关于计算奖金的一个部分。
这种方式明显比为基本的计算奖金的对象增长子类来得更灵活,由于装饰模式的起源点是采用对象组合的方式,而后在组合的时候顺便增长些功能。为了达到一层一层组装的效果,装饰模式还要求装饰器要实现与被装饰对象相同的业务接口,这样才能以同一种方式依次组合下去。
灵活性还体如今动态上,若是是继承的方式,那么全部的类实例都有这个功能了,而采用装饰模式,能够动态的为某几个对象实例添加功能,而不是对整个类添加功能。好比上面示例中,客户端测试的时候,对张三李四就只是组合了两个功能,对王五就组合了三个功能,可是原始的计算奖金的类都是同样的,只是动态的为它增长的功能不一样而已。
原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/0/5667.html】