装饰者模式(Decorator)

众所周知,如果要加强一个类或对象的功能可以通过继承然后重写父类方法或者通过装饰者模式的方法对已有对象功能进行加强和优化。通过继承的方式可以使子类具有父类的属性和方法。子类继承父类后,因为一些业务需求可以通过重写的方式来加强父类的方法的一些功能,也可以重新定义某些属性,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。而装饰者模式的最基本的功能就是对传入的一个对象进行功能的加强与优化。那么问题来了,既然继承方式也可以对已有类或对象进行加强,那为什么还要衍生出装饰者模式这一思想呢?
装饰者模式的意图定义为:动态地给一个对象添加一些额外的职责。单单就这简短的一句话就可以知道,除了最基本的增强已有对象的功能外,恐怕装饰者模式存在的更重要的意义就在于动态的为对象添加一些功能(或分配额外职责),在我理解看来,继承和装饰者模式的关系与代理模式中的静态代理和动态代理的关系是有异曲同工之妙的。
下面我们看一下装饰者模式的UML图和代码结构
装饰者模式(Decorator)
文章图片
UML.png

//定义一个对象接口,可以给这些对象动态地添加职责。 public interface Component{ void operation(); }

//定义一个对象,可以给这个对象添加一些职责。 public class ConcreteComponent implements Component { public void operation() { // Write your code here } }

//装饰者父类, 默认直接调用component的方法 public class Decorator implements Component{ public Decorator(Component component){ this.component = component; } public void operation(){ component.operation(); } private Component component; } // 以后的装饰者子类,重写父类的operator()方法,在里面加入自己的东西即可。

说到这里,它到底解决了什么样的问题呢?网上找到一个简单示例来具体分析一下:
相信大家都有喝过豆浆,那么假设现在一杯纯豆浆(Soya)卖1元钱,你可以选择往里边加糖0.5元(Suger),加蜂蜜0.5元(Honey),加牛奶1元(Milk),加黑豆1元(BlackBean),加鸡蛋(1元)等等。如果要计算出任意组合的豆浆的价钱该怎么做呢?
下面让我们先尝试用继承的方式分析一下:设豆浆类为基类,每个类中有一个money属性,那么豆浆加牛奶可模拟为Soya类继承Milk并重写pay()方法,如此继承确实可以计算出每种组合的价钱,但是请看下图:
装饰者模式(Decorator)
文章图片
820776-20151027154625982-770317673.png 会造成子类数据过多难以维护。因此采用继承虽然可行,但是会造成代码臃肿,扩展性不好,不灵活,类与类之间耦合度高。那么该如何解决这个问题呢?这时就要用到今天的主角:装饰者模式。
首先抽象出一个接口,作为装饰者构造函数的参数,即被装饰者的父类:
package DecoratorMethod; public interface Drink {public float money(); //获取价格。public String description(); //返回商品信息。}

【装饰者模式(Decorator)】接下来就是装饰者类,继承此接口并通过构造方法获取被装饰对象公共接口,重写装饰方法:
package DecoratorMethod; public abstract class Decorator implements Drink{private Drink drink; public Decorator (Drink drink){this.drink = drink; }public String description() {return drink.description(); }public float cost() {return drink.money(); }}

package DecoratorMethod; /** * 具体的被装饰对象,纯豆浆Soya。 */ public class Soya implements Drink{public String description() {return "纯豆浆"; }public float money() {return 5f; }}

下面分别是装饰者:Suger,Milk,BlackBean,Honey类:
package DecoratorMethod; /* * 具体的装饰者类:糖 */ public class Suger extends Decorator {public Suger(Drink drink) {super(drink); }public String description() {return super.description()+"+糖"; }public float money() {return super.money()+1.5f; }}

package DecoratorMethod; /** * 具体的装饰者类:牛奶。 * */ public class Milk extends Decorator {public Milk(Drink drink) {super(drink); }public String description() {return super.description()+"+牛奶"; }public float money() {return super.money()+1.5f; }}

package DecoratorMethod; /* * 具体的装饰者类:蜂蜜 */ public class Honey extends Decorator {public Honey(Drink drink) {super(drink); }public String description() {return super.description()+"+蜂蜜"; }public float money() {return super.money()+1.5f; }}

package DecoratorMethod; /* * 具体的装饰者类:黑豆。 */ public class BlackBean extends Decorator{public BlackBean(Drink drink) {super(drink); }public String description() {return super.description()+"+黑豆"; }public float money() {return super.money()+2.5f; }}

下面是测试类代码:
package DecoratorMethod; /* *测试类 */ public class TestDemo {public static void main(String[] args) {//定义一杯纯豆浆Soya soya = new Soya(); System.out.println(soya.description()+" 价钱:"+soya.money()); System.out.println("-----------------"); //豆浆加奶Milk milkSoya = new Milk(soya); System.out.println(milkSoya.description()+" 价钱:"+milkSoya.money()); System.out.println("-----------------"); //黑豆豆浆+奶BlackBean beanMilkSoya = new BlackBean(milkSoya); System.out.println(beanMilkSoya.description()+" 价钱:"+beanMilkSoya.money()); System.out.println("-----------------"); //黑豆豆浆+奶+蜂蜜Honey honeyBeanMilkSoya = new Honey(beanMilkSoya); System.out.println(honeyBeanMilkSoya.description()+" 价钱:"+honeyBeanMilkSoya.money()); }}

运行结果如下:

装饰者模式(Decorator)
文章图片
1.jpg 其实对于装饰者模式大家平常自己写代码写得比较少,但是也常见到的,不少框架项目使用了装饰模式, 例如在JDK的java.io.*包中就大量使用装饰模式。比如大家最常用的语句:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath))); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

就是最常见的装饰者模式了,通过BufferedReader对已有对象FileReader的功能进行加强和优化。其实它不仅可以加强FileReader,所有的字符输入流都可以通过这种方式进行包装。它是如何实现的呢?注意重点来了:其实很简单,它只不过将所有的字符输入流抽象出了一个基类或接口即Reader,然后通过构造方法的形式将Reader传递给BufferedReader,此时BufferedReader就可以对所有的字符输入流进行拦截和优化了。
试想一下,如果采用继承机制,每个XXXReader就要衍生出一个BufferedXXXReader,再加上字符输出流和字节输入输出流,那么Java的IO体系结构该是多么的臃肿不堪啊!而装饰者模式的出现解决了这个问题,并且,装饰者的出现也再一次的证明了面向对象的设计原则:多用组合,少用继承!对扩展开放,对修改关闭!
读到这里,想必大家都知道装饰者模式存在的价值了吧,下面对装饰者模式的特点和应用场景进行总结:
特点:
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。
适应性:
(1) 需要扩展一个类的功能,或给一个类添加附加职责。
(2) 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
(3) 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
(4) 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。

    推荐阅读