装饰模式

装饰模式(Decorator)

1  场景问题

1.1  复杂的奖金计算

        考虑这样一个实际应用:就是如何实现灵活的奖金计算。
        奖金计算是相对复杂的功能,尤为是对于业务部门的奖金计算方式,是很是复杂的,除了业务功能复杂外,另一个麻烦之处是计算方式还常常须要变更,由于业务部门常常经过调整奖金的计算方式来激励士气。
        先从业务上看看现有的奖金计算方式的复杂性:html

  • 首先是奖金分类:对于我的,大体有我的当月业务奖金、我的累计奖金、我的业务增加奖金、及时回款奖金、限时成交加码奖金等等;java

  • 对于业务主管或者是业务经理,除了我的奖金外,还有:团队累计奖金、团队业务增加奖金、团队盈利奖金等等。算法

  • 其次是计算奖金的金额,又有这么几个基数:销售额、销售毛利、实际回款、业务成本、奖金基数等等;数据库

  • 另一个就是计算的公式,针对不一样的人、不一样的奖金类别、不一样的计算奖金的金额,计算的公式是不一样的,就算是同一个公式,里面计算的比例参数也有多是不一样的。设计模式

 

1.2  简化后的奖金计算体系

        看了上面奖金计算的问题,所幸咱们只是来学习设计模式,并非真的要去实现整个奖金计算体系的业务,所以也没有必要把全部的计算业务都罗列在这里,为了后面演示的须要,简化一下,演示用的奖金计算体系以下:app

  • 每一个人当月业务奖金 = 当月销售额 X  3%ide

  • 每一个人累计奖金 = 总的回款额 X  0.1%学习

  • 团队奖金 = 团队总销售额 X 1%测试

 

1.3  不用模式的解决方案

        一我的的奖金分红不少个部分,要实现奖金计算,主要就是要按照各个奖金计算的规则,把这我的能够获取的每部分奖金计算出来,而后计算一个总和,这就是这我的能够获得的奖金。
(1)为了演示,先准备点测试数据,在内存中模拟数据库,示例代码以下:this

/** 
 * 在内存中模拟数据库,准备点测试数据,好计算奖金 
 */  
public class TempDB {  
    private TempDB(){  
}  
    /** 
     * 记录每一个人的月度销售额,只用了人员,月份没有用 
     */  
    public static Map<String,Double> mapMonthSaleMoney =   
new HashMap<String,Double>();  
    static{  
        //填充测试数据  
        mapMonthSaleMoney.put("张三",10000.0);  
        mapMonthSaleMoney.put("李四",20000.0);  
        mapMonthSaleMoney.put("王五",30000.0);  
    }  
}

(2)按照奖金计算的规则,实现奖金计算,示例代码以下:

/** 
 * 计算奖金的对象 
 */  
public class Prize {  
    /** 
     * 计算某人在某段时间内的奖金,有些参数在演示中并不会使用, 
     * 可是在实际业务实现上是会用的,为了表示这是个具体的业务方法, 
     * 所以这些参数被保留了 
     * @param user 被计算奖金的人员 
     * @param begin 计算奖金的开始时间 
     * @param end 计算奖金的结束时间 
     * @return 某人在某段时间内的奖金 
     */  
    public  double calcPrize(String user,Date begin,Date end){  
        double prize = 0.0;   
        //计算当月业务奖金,全部人都会计算  
        prize = this.monthPrize(user, begin, end);  
        //计算累计奖金  
        prize += this.sumPrize(user, begin, end);  
          
        //须要判断该人员是普通人员仍是业务经理,团队奖金只有业务经理才有  
        if(this.isManager(user)){  
            prize += this.groupPrize(user, begin, end);  
        }  
        return prize;  
    }  
  
    /** 
     * 计算某人的当月业务奖金,参数重复,就再也不注释了 
     */  
    private double monthPrize(String user, Date begin, Date end) {  
        //计算当月业务奖金,按照人员去获取当月的业务额,而后再乘以3%  
        double prize = TempDB.mapMonthSaleMoney.get(user) * 0.03;  
        System.out.println(user+"当月业务奖金"+prize);  
        return prize;  
    }  
  
    /** 
     * 计算某人的累计奖金,参数重复,就再也不注释了 
     */  
    public double sumPrize(String user, Date begin, Date end) {  
        //计算累计奖金,其实应该按照人员去获取累计的业务额,而后再乘以0.1%  
        //简单演示一下,假定你们的累计业务额都是1000000元  
        double prize = 1000000 * 0.001;  
        System.out.println(user+"累计奖金"+prize);  
        return prize;  
    }     
  
    /** 
     * 判断人员是普通人员仍是业务经理 
     * @param user 被判断的人员 
     * @return true表示是业务经理,false表示是普通人员 
     */  
    private boolean isManager(String user){  
        //应该从数据库中获取人员对应的职务  
        //为了演示,简单点判断,只有王五是经理  
        if("王五".equals(user)){  
            return true;              
        }  
        return false;  
    }  
    /** 
     * 计算当月团队业务奖,参数重复,就再也不注释了 
     */  
    public double groupPrize(String user, Date begin, Date end) {  
        //计算当月团队业务奖金,先计算出团队总的业务额,而后再乘以1%,  
//假设都是一个团队的  
        double group = 0.0;  
        for(double d : TempDB.mapMonthSaleMoney.values()){  
            group += d;  
        }  
        double prize = group * 0.01;  
        System.out.println(user+"当月团队业务奖金"+prize);  
        return prize;  
    }  
}

(3)写个客户端来测试一下,看看是否能正确地计算奖金,示例代码以下:

public class Client {  
    public static void main(String[] args) {  
        //先建立计算奖金的对象  
        Prize p = new Prize();  
          
        //日期对象都没有用上,因此传null就能够了  
        double zs = p.calcPrize("张三",null,null);          
        System.out.println("==========张三应得奖金:"+zs);  
        double ls = p.calcPrize("李四",null,null);  
        System.out.println("==========李四应得奖金:"+ls);       
        double ww = p.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

2.1装饰模式来解决

(1)装饰模式定义

  动态地给一个对象添加一些额外的职责。就增长功能来讲,装饰模式比生成子类更为灵活。

(2)应用装饰模式来解决的思路
        虽然通过简化,业务简单了不少,可是须要解决的问题不会少,仍是要解决:要透明的给一个对象增长功能,并实现功能的动态组合。
        所谓透明的给一个对象增长功能,换句话说就是要给一个对象增长功能,可是不能让这个对象知道,也就是不能去改动这个对象。而实现了可以给一个对象透明的增长功能,天然就可以实现功能的动态组合,好比原来的对象有A功能,如今透明的给它增长了一个B功能,是否是就至关于动态组合了A和B功能呢。
        要想实现透明的给一个对象增长功能,也就是要扩展对象的功能了,使用继承啊,有人立刻提出了一个方案,但很快就被否决了,那要减小或者修改功能呢?事实上继承是很是不灵活的复用方式。那就用“对象组合”嘛,又有人提出新的方案来了,这个方案获得了你们的赞同。
        在装饰模式的实现中,为了可以和原来使用被装饰对象的代码实现无缝结合,是经过定义一个抽象类,让这个类实现与被装饰对象相同的接口,而后在具体实现类里面,转调被装饰的对象,在转调的先后添加新的功能,这就实现了给被装饰对象增长功能,这个思路跟“对象组合”很是相似。若是对“对象组合”不熟悉,请参见3.1的第2小节。
在转调的时候,若是以为被装饰的对象的功能再也不须要了,还能够直接替换掉,也就是再也不转调,而是在装饰对象里面彻底全新的实现。

2.2  模式结构和说明

wKioL1ljOPzzAC_xAACIWxBEs1M805.png

Component:
        组件对象的接口,能够给这些对象动态的添加职责。
ConcreteComponent:
        具体的组件对象,实现组件对象接口,一般就是被装饰器装饰的原始对象,也就是能够给这个对象添加职责。
Decorator:
        全部装饰器的抽象父类,须要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
        注意这个被装饰的对象不必定是最原始的那个对象了,也多是被其它装饰器装饰事后的对象,反正都是实现的同一个接口,也就是同一类型。
ConcreteDecorator:
        实际的装饰器对象,实现具体要向被装饰对象添加的功能。

2.3  装饰模式示例代码

(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();  
    }  
}

2.4  使用装饰模式示例

演示用的奖金计算体系以下:

  • 每一个人当月业务奖金 = 当月销售额 X  3%

  • 每一个人累计奖金 = 总的回款额 X  0.1%

  • 团队奖金 = 团队总销售额 X 1%

 大体内容以下:

  • 首先须要定义一个组件对象的接口,在这个接口里面定义计算奖金的业务方法,由于外部就是使用这个接口来操做装饰模式构成的对象结构中的对象

  • 须要添加一个基本的实现组件接口的对象,可让它返回奖金为0就能够了

  • 把各个计算奖金的规则看成装饰器对象,须要为它们定义一个统一的抽象的装饰器对象,好约束各个具体的装饰器的接口

  • 把各个计算奖金的规则实现成为具体的装饰器对象

先看看如今示例的总体结构,好总体理解和把握示例,如图所示:

wKioL1ljPICwwYwBAABXWygwBJg029.png

(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

当测试运行的时候会按照装饰器的组合顺序,依次调用相应的装饰器来执行业务功能,是一个递归的调用方法,以业务经理“王五”的奖金计算作例子,画个图来讲明奖金的计算过程吧,看看是如何调用的,如图所示

wKioL1ljPrLiUO6VAAAxBi4Ha5U703.png

如同上面的示例,对于基本的计算奖金的对象而言,因为计算奖金的逻辑太过于复杂,并且须要在不一样的状况下进行不一样的运算,为了灵活性,把多种计算奖金的方式分散到不一样的装饰器对象里面,采用动态组合的方式,来给基本的计算奖金的对象增添计算奖金的功能,每一个装饰器至关于计算奖金的一个部分。
       这种方式明显比为基本的计算奖金的对象增长子类来得更灵活,由于装饰模式的起源点是采用对象组合的方式,而后在组合的时候顺便增长些功能。为了达到一层一层组装的效果,装饰模式还要求装饰器要实现与被装饰对象相同的业务接口,这样才能以同一种方式依次组合下去。
       灵活性还体如今动态上,若是是继承的方式,那么全部的类实例都有这个功能了,而采用装饰模式,能够动态的为某几个对象实例添加功能,而不是对整个类添加功能。好比上面示例中,客户端测试的时候,对张三李四就只是组合了两个功能,对王五就组合了三个功能,可是原始的计算奖金的类都是同样的,只是动态的为它增长的功能不一样而已。

3.1  Java中的装饰模式应用

1:本身实现的I/O流的装饰器

        要让咱们写的装饰器跟其它Java中的装饰器同样用,最合理的方案就应该是:让咱们的装饰器继承装饰器的父类,也就是FilterOutputStream类,而后使用父类提供的功能来协助完成想要装饰的功能。示例代码以下:

public class EncryptOutputStream2  extends FilterOutputStream{
    private OutputStream os = null;
    public EncryptOutputStream2(OutputStream os){
       //调用父类的构造方法
       super(os);
    }
    public void write(int a) throws IOException {
       //先统一贯后移动两位
       a = a+2;
       //97是小写的a的码值
       if(a >= (97+26)){
           //若是大于,表示已是y或者z了,减去26就回到a或者b了
           a = a-26;
       }
       //调用父类的方法
       super.write(a);
    }
}

测试一下看看,好用吗?客户端使用代码示例以下:

public class Client {
	public static void main(String[] args) throws Exception {
		//流式输出文件
		DataOutputStream dout = new DataOutputStream(
			new EncryptOutputStream (
				new BufferedOutputStream(
					new FileOutputStream("MyEncrypt.txt"))));
		dout.write("abcdxyz".getBytes());
		dout.close();
	}
}

运行一下,打开生成的文件,看看结果,结果示例以下:

cdefzab

3.2使用装饰模式作出相似AOP的效果
        下面来演示一下使用装饰模式,把一些公共的功能,好比权限控制,日志记录,透明的添加回到业务功能模块中去,作出相似AOP的效果。
(1)首先定义业务接口
        这个接口至关于装饰模式的Component。注意这里使用的是接口,而不像前面同样使用的是抽象类,虽然使用抽象类的方式来定义组件是装饰模式的标准实现方式,可是若是不须要为子类提供公共的功能的话,也是能够实现成接口的,这点要先说明一下,省得有些朋友会认为这就不是装饰模式了,示例代码以下:

/** 
* 商品销售管理的业务接口 
*/  
public interface GoodsSaleEbi {  
    /** 
     * 保存销售信息,原本销售数据应该是多条,太麻烦了,为了演示,简单点 
     * @param user 操做人员 
     * @param customer 客户 
     * @param saleModel 销售数据 
     * @return 是否保存成功 
     */  
    public boolean sale(String user,String customer,  
SaleModel saleModel);  
}

顺便把封装业务数据的对象也定义出来,很简单,示例代码以下:

/** 
* 封装销售单的数据,简单的示意一些 
*/  
public class SaleModel {  
    /** 
    * 销售的商品 
    */  
    private String goods;  
    /** 
    * 销售的数量 
    */  
    private int saleNum;  
    public String getGoods() {    
        return goods;     
    }  
    public void setGoods(String goods) {  
        this.goods = goods;   
    }  
    public int getSaleNum() {  
        return saleNum;  
    }  
    public void setSaleNum(int saleNum) {  
        this.saleNum = saleNum;  
    }  
    public String toString(){  
        return "商品名称="+goods+",购买数量="+saleNum;  
    }  
}

(2)定义基本的业务实现对象,示例代码以下:

public class GoodsSaleEbo implements GoodsSaleEbi{  
    public boolean sale(String user,String customer,   
                      SaleModel saleModel) {  
        System.out.println(user+"保存了"  
                                 +customer+"购买 "+saleModel+" 的销售数据");  
        return true;  
    }  
}

(3)接下来该来实现公共功能了,把这些公共功能实现成为装饰器,那么须要给它们定义一个抽象的父类,示例以下:

/** 
* 装饰器的接口,须要跟被装饰的对象实现一样的接口 
*/  
public abstract class Decorator implements GoodsSaleEbi{  
    /** 
               * 持有被装饰的组件对象 
           */  
    protected GoodsSaleEbi ebi;  
    /** 
     * 经过构造方法传入被装饰的对象 
     * @param ebi被装饰的对象 
     */  
    public Decorator(GoodsSaleEbi ebi){  
        this.ebi = ebi;  
    }  
}

(4)实现权限控制的装饰器
先检查是否有运行的权限,若是有就继续调用,若是没有,就不递归调用了,而是输出没有权限的提示,示例代码以下:

/** 
 * 实现权限控制 
 */  
public class CheckDecorator extends Decorator{  
    public CheckDecorator(GoodsSaleEbi ebi){  
        super(ebi);  
    }  
    public boolean sale(String user,String customer  
        , SaleModel saleModel) {  
        //简单点,只让张三执行这个功能  
        if(!"张三".equals(user)){  
            System.out.println("对不起"+user  
                +",你没有保存销售单的权限");  
            //就再也不调用被装饰对象的功能了  
            return false;  
        }else{  
            return this.ebi.sale(user,customer,saleModel);  
        }         
    }  
}

(5)实现日志记录的装饰器,就是在功能执行完成后记录日志便可,示例代码以下:

/** 
* 实现日志记录 
*/  
public class LogDecorator extends Decorator{  
    public LogDecorator(GoodsSaleEbi ebi){  
        super(ebi);  
    }  
    public boolean sale(String user,String customer,   
        SaleModel saleModel) {  
        //执行业务功能  
        boolean f = this.ebi.sale(user, customer, saleModel);  
  
        //在执行业务功能事后,记录日志  
        DateFormat df =   
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");  
        System.out.println("日志记录:"+user+"于"+  
                df.format(new Date())+"时保存了一条销售记录,客户是"  
                +customer+",购买记录是"+saleModel);  
        return f;  
    }  
}

(6)组合使用这些装饰器
        在组合的时候,权限控制应该是最早被执行的,因此把它组合在最外面,日志记录的装饰器会先调用原始的业务对象,因此把日志记录的装饰器组合在中间。
        前面讲过,装饰器之间最好不要有顺序限制,可是在实际应用中,要根据具体的功能要求来,有须要的时候,也能够有顺序的限制,但应该尽可能避免这种状况。
        此时客户端测试代码示例以下:

public class Client {  
    public static void main(String[] args) {  
        //获得业务接口,组合装饰器  
        GoodsSaleEbi ebi = new CheckDecorator(  
                new LogDecorator(  
                new GoodsSaleEbo()));  
        //准备测试数据  
        SaleModel saleModel = new SaleModel();  
        saleModel.setGoods("Moto手机");  
        saleModel.setSaleNum(2);  
        //调用业务功能  
        ebi.sale("张三","张三丰", saleModel);  
        ebi.sale("李四","张三丰", saleModel);  
    }  
}

运行结果以下:

wKiom1ljg6XzQDfWAAFsnPDv6ls005.png

好好体会一下,是否是也在没有惊动原始业务对象的状况下,给它织入了新的功能呢?也就是说是在原始业务不知情的状况下,给原始业务对象透明的增长了新功能,从而模拟实现了AOP的功能。
        事实上,这种作法,彻底能够应用在项目开发上,在后期为项目的业务对象添加数据检查、权限控制、日志记录等功能,就不须要在业务对象上去处理这些功能了,业务对象能够更专一于具体业务的处理。

3.3  装饰模式的优缺点

  • 比继承更灵活
        从为对象添加功能的角度来看,装饰模式比继承来得更灵活。继承是静态的,并且一旦继承是全部子类都有同样的功能。而装饰模式采用把功能分离到每一个装饰器当中,而后经过对象组合的方式,在运行时动态的组合功能,每一个被装饰的对象,最终有哪些功能,是由运行期动态组合的功能来决定的。

  • 更容易复用功能
        装饰模式把一系列复杂的功能,分散到每一个装饰器当中,通常一个装饰器只实现一个功能,这样实现装饰器变得简单,更重要的是这样有利于装饰器功能的复用,能够给一个对象增长多个一样的装饰器,也能够把一个装饰器用来装饰不一样的对象,从而复用装饰器的功能。

  • 简化高层定义
        装饰模式能够经过组合装饰器的方式,给对象增添任意多的功能,所以在进行高层定义的时候,不用把全部的功能都定义出来,而是定义最基本的就能够了,能够在使用须要的时候,组合相应的装饰器来完成须要的功能。

  • 会产生不少细粒度对象
        前面说了,装饰模式是把一系列复杂的功能,分散到每一个装饰器当中,通常一个装饰器只实现一个功能,这样会产生不少细粒度的对象,并且功能越复杂,须要的细粒度对象越多。

3.4  相关模式

  • 装饰模式与适配器模式
        这是两个没有什么关联的模式,放到一块儿来讲,是由于它们有一个共同的别名:Wrapper。
        这两个模式功能上是不同的,适配器模式是用来改变接口的,而装饰模式是用来改变对象功能的。

  • 装饰模式与组合模式
        这两个模式有类似之处,都涉及到对象的递归调用,从某个角度来讲,能够把装饰当作是只有一个组件的组合。
        可是它们的目的彻底不同,装饰模式是要动态的给对象增长功能;而组合模式是想要管理组合对象和叶子对象,为它们提供一个一致的操做接口给客户端,方便客户端的使用。

  • 装饰模式与策略模式
        这两个模式能够组合使用。
        策略模式也能够实现动态的改变对象的功能,可是策略模式只是一层选择,也就是根据策略选择一下具体的实现类而已。而装饰模式不是一层,而是递归调用,无数层均可以,只要组合好装饰器的对象组合,那就能够依次调用下去,因此装饰模式会更灵活。
        并且策略模式改变的是原始对象的功能,不像装饰模式,后面一个装饰器,改变的是通过前一个装饰器装饰事后的对象,也就是策略模式改变的是对象的内核,而装饰模式改变的是对象的外壳。
        这两个模式能够组合使用,能够在一个具体的装饰器里面使用策略模式,来选择更具体的实现方式。好比前面计算奖金的另一个问题就是参与计算的基数不一样,奖金的计算方式也是不一样的。举例来讲:假设张三和李四参与同一个奖金的计算,张三的销售总额是2万元,而李四的销售额是8万元,它们的计算公式是不同的,假设奖金的计算规则是,销售额在5万如下,统一3%,而5万以上,5万内是4%,超过部分是6%。
        参与同一个奖金的计算,这就意味着可使用同一个装饰器,可是在装饰器的内部,不一样条件下计算公式不同,那么怎么选择具体的实现策略呢?天然使用策略模式就行了,也就是装饰模式和策略模式组合来使用。

  • 装饰模式与模板方法模式
        这是两个功能上有类似点的模式。
        模板方法模式主要应用在算法骨架固定的状况,那么要是算法步骤不固定呢,也就是一个相对动态的算法步骤,就可使用装饰模式了,由于在使用装饰模式的时候,进行装饰器的组装,其实也至关因而一个调用算法步骤的组装,至关因而一个动态的算法骨架。
        既然装饰模式能够实现动态的算法步骤的组装和调用,那么把这些算法步骤固定下来,那就是模板方法模式实现的功能了,所以装饰模式能够模拟实现模板方法模式的功能。
        可是请注意,仅仅只是能够模拟功能而已,两个模式的设计目的、本来的功能、本质思想等都是不同的。

转载至:http://sishuok.com/forum/blogPost/list/113.html

   cc老师的设计模式是我目前看过最详细最有实践的教程。

相关文章
相关标签/搜索