1 基础知识
定义:在不改变原有对象的基础上,将功能附加到对象上即动态地给一个对象添加一些额外的职责。特征:提供了比继承更有弹性的替代方案。
本质:动态组合。
使用场景:扩展一个类的功能或给一个类添加附加的职责;动态给一个对象添加功能,这些功能还可以动态撤销。
优点:比继承更加灵活,继承是静态的在设计之初就确定好了而装饰者是动态的扩展功能;通过使用不同的装饰类或装饰类的排列组合可以达到不同的效果;符合开闭原则。缺点:会出现更多的代码,更多的类使程序复杂;动态装饰时,多层装饰会更复杂。
2 代码示例
场景:设若有一个计算奖金的对象,现在需要能够灵活地给它增加和减少功能,还需要能够动态地组合功能,每个功能就相当于在计算奖金的某个部分。现在的问题就是,如何才能够透明地给一个对象增加功能,并实现功能的动态组合?在此种情况下可以用到装饰者模式,所谓透明增加功能就是给一个对象增加功能但是不能让对象知道。在使用时可以考虑一个更为简单的场景:假设商家做了许多煎饼,这些煎饼有的加蛋,有的加烤肠那么如何计算出这些不同种类的的煎饼?
煎饼类:Battercake
public class Battercake { //描述 protected String getDesc(){ return "煎饼"; } //煎饼的基本价格 protected int cost(){ return 8; } }
煎饼加鸡蛋类:BattercakeWithEgg 继承煎饼类
public class BattercakeWithEgg extends Battercake { @Override public String getDesc() { return super.getDesc()+" 加一个鸡蛋"; } @Override //重写父类花费方法 public int cost() { return super.cost()+1; } }
煎饼加鸡蛋类加香肠类:BattercakeWithEggSausage 继承煎饼加鸡蛋类
public class BattercakeWithEggSausage extends BattercakeWithEgg { @Override public String getDesc() { return super.getDesc()+ " 加一根香肠"; } @Override public int cost() { return super.cost()+2; } }
应用层:Test
public class Test { public static void main(String[] args) { //基本的煎饼 Battercake battercake = new Battercake(); System.out.println(battercake.getDesc()+" 销售价格:"+battercake.cost()); //加鸡蛋的类 Battercake battercakeWithEgg = new BattercakeWithEgg(); System.out.println(battercakeWithEgg.getDesc()+" 销售价格:"+battercakeWithEgg.cost()); //加鸡蛋加香肠的类 Battercake battercakeWithEggSausage = new BattercakeWithEggSausage(); System.out.println(battercakeWithEggSausage.getDesc()+" 销售价格:"+battercakeWithEggSausage.cost()); } }
此时如果想加两个鸡蛋,加三个香肠等则需要分别建立对应的类这样可以预见一定会随着的需求的复杂而使得的类的数目上升,造成类爆炸。那么对于这种情形的解决方案便是装饰者模式。在装饰者模式中要有抽象的实体类、具体的实体类,抽象的装饰者、具体的装饰者根据这些来编写代码。
抽象的实体类:ABattercake
public abstract class ABattercake { protected abstract String getDesc(); protected abstract int cost(); }
具体的实体类煎饼类:Battercake 继承抽象实体类
public class Battercake extends ABattercake { @Override protected String getDesc() { return "煎饼"; } @Override protected int cost() { return 8; } }
抽象的装饰类:AbstractDecorator
//抽象的装饰者继承了抽象的实体 public abstract class AbstractDecorator extends Battercake { private ABattercake aBattercake; //通过构造器注入对象 public AbstractDecorator(ABattercake aBattercake) { this.aBattercake = aBattercake; } protected abstract void doSomething(); @Override protected String getDesc() { return this.aBattercake.getDesc(); } @Override protected int cost() { return this.aBattercake.cost(); } }
具体的装饰类香肠类:SausageDecorator 继承抽象装饰者
public class SausageDecorator extends AbstractDecorator{ //因为父类中没有无参构造所以要在子类中使用有参构造调用父类的的有参构造 // 因此要在子类中实现默认的构造器 public SausageDecorator(ABattercake aBattercake) { super(aBattercake); } @Override protected void doSomething() { } @Override protected String getDesc() { return super.getDesc()+" 加一根香肠"; } @Override protected int cost() { return super.cost()+2; } }
具体的装饰者类鸡蛋类:EggDecorator
public class EggDecorator extends AbstractDecorator { public EggDecorator(ABattercake aBattercake) { super(aBattercake); } @Override protected void doSomething() { } @Override protected String getDesc() { return super.getDesc()+" 加一个鸡蛋"; } @Override protected int cost() { return super.cost()+1; } }
应用层:
public class Test { public static void main(String[] args) { ABattercake aBattercake = new Battercake(); //加两个鸡蛋 aBattercake = new EggDecorator(aBattercake); aBattercake = new EggDecorator(aBattercake); //加一个香肠 aBattercake = new SausageDecorator(aBattercake); System.out.println(aBattercake.getDesc()+" 销售价格:"+aBattercake.cost()); } }
类关系图:
注:
在抽象的装饰类中有一个dosomething方法,这个方法在这个业务逻辑中只是为了让装饰类是抽象的而已并无任何业务逻辑,从这里也可以看出抽象的装饰类在不同的业务情况下也可以不是抽象的。
3 源代码中的使用
(1)JDK中
IO包下的BufferedReader类:
public class BufferedReader extends Reader { private Reader in; public BufferedReader(Reader in, int sz) { super(in); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.in = in cb = new char[sz];
}
Reader是一个抽象的类,就相当于抽象的煎饼类,BufferedReader上面中的抽象的装饰者类,通过构造器注入Reader对象,在这里承担抽象装饰者类的角色但不是抽象类。
在IO流中对于输入流其类图关系如下:
InputStream就是抽象的装饰者类,FilterInputStream是装饰者,但因为装饰者比较多因此做了一层封装,下面的LineInputStream、BufferdInputStream和DataInputStream是具体的装饰者。与此类似的还有输出流中的类OutputStream。
4 相关模式
(1)装饰模式与适配器模式
这是两个没有什么关联的模式,放到一起来说,是因为它们有一个共同的别名:Wrapper这两个模式功能上是不一样的,适配器模式是用来改变接口的,而装饰模式是用来改变对象功能的。
(2)装饰模式与组合模式
这两个模式有相似之处,都涉及到对象的递归调用,从某个角度来说,可以把装饰看做是只有一个组件的组合。但是它们的目的完全不一样,装饰模式是要动态地给对象增加功能;而组合模式是想要管理组合对象和叶子对象,为它们提供一个一致的操作接口给客户端,方便客户端的使用。
(3)装饰模式与策略模式
这两个模式可以组合使用。策略模式也可以实现动态地改变对象的功能,但是策略模式只是一层选择,也就是根据策略选择一下具体的实现类而已。而装饰模式不是一层,而是递归调用,无数层都可以,只要组合好装饰器的对象组合,那就可以依次调用下去。所以装饰模式更灵活。而且策略模式改变的是原始对象的功能,不像装饰模式,后面一个装饰器,改变的是经过前一个装饰器装饰后的对象。也就是策略模式改变的是对象的内核,而装饰模式改变的是对象的外壳。
这两个模式可以组合使用,可以在一个具体的装饰器中使用策略模式来选择更具体的实现方式。比如前面计算奖金的另外一个问题就是参与计算的基数不同,奖金的计算方式也是不同的。举例来说:假设张三和李四参与同一个奖金的计算,张三的销售总额是2万元,而李四的销售总额是8万元,它们的计算公式是不一样的,假设奖金的计算规则是,销售额在5万以下,统一3%,而5万以上,5万内是4%,超过部分是6%参与同一个奖金的计算,这就意味着可以使用同一个装饰器,但是在装饰器的内部,不同条件下计算公式不一样,那么怎么选择具体的实现策略呢?自然使用策略模式就可以了,也就是装饰模式和策略模式组合来使用。
(4)装饰模式与模板方法模式
这是两个功能上有相似点的模式。模板方法模式主要应用在算法骨架固定的情况,那么要是算法步骤不固定呢,也就是一个相对动态的算法步骤,就可以使用装饰模式了,因为在使用装饰模式的时候,进行装饰器的组装,其实也相当于是一个调用算法步骤的组装,相当于是一个动态的算法骨架。既然装饰模式可以实现动态的算法步骤的组装和调用,那么把这些算法步骤固定下来,那就是模板方法模式实现的功能了,因此装饰模式可以模拟实现模板方法模式的功能。
0