浅析面向对象之——装饰者模式之于继承机制的存在价值与意义
相信很多小伙伴们都对装饰者模式有所了解了吧,装饰者模式是java的23种设计模式之一【如果还有人不是太了解的话建议去学习一下,因为面试题常常会设计到设计模式】 可能我们都已经知道如果要加强一个类或对象的功能可以通过继承然后重写父类方法或者通过装饰者模式的方法对已有对象功能进行加强和优化,那么为什么有了继承机制之后还要再衍生出装饰者模式的设计思想呢?
下面先简单分析一下装饰者模式和继承方式的共同点:
大家都知道,通过继承的方式可以使子类具有父类的属性和方法。子类继承父类后,因为一些业务需求可以通过重写的方式来加强父类的方法的一些功能,也可以重新定义某些属性,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。而装饰者模式的最基本的功能就是对传入的一个对象进行功能的加强与优化。那么问题来了,既然继承方式也可以对已有类或对象进行加强,那为什么还要衍生出装饰者模式这一思想呢?重头戏在下面:
装饰者模式的意图定义为:动态地给一个对象添加一些额外的职责。单单就这简短的依据话就可以知道,除了最基本的增强已有对象的功能外,恐怕装饰者模式存在的更重要的意义就在于动态的为对象添加一些功能(或分配额外职责)。那么这句话到底是什么意思呢?它到底解决了什么样的问题呢?下面通过一些最简单的示例来具体分析一下:
就拿大家日常生活中的例子来说吧,相信大家都有喝过豆浆。那么假设现在一杯纯豆浆(Soya)卖1元钱,你可以选择往里边加糖0.5元(Suger),加蜂蜜0.5元(Honey),加牛奶1元(Milk),加黑豆1元(BlackBean),加鸡蛋(1元)等等。如果要计算出任意组合的豆浆的价钱该怎么做呢?
下面让我们先尝试用继承的方式分析一下:设豆浆类为基类,每个类中有一个money属性,那么豆浆加牛奶可模拟为Soya类继承Milk并重写pay()方法,如此继承确实可以计算出每种组合的价钱,但是请看下图:
会造成子类数据过多难以维护。因此采用继承虽然可行,但是会造成代码臃肿,扩展性不好,不灵活,类与类之间耦合度高。那么该如何解决这个问题呢?这时就要用到今天的主角:装饰者模式 了。
其实对于装饰者模式大家并不陌生,相反接触的还很平凡,因为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. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。
下面是关于上述豆浆例子的代码实现,感兴趣的小伙伴们可以继续阅读:
首先抽象出一个接口,作为装饰者构造函数的参数,即被装饰者的父类:
package DecoratorMethod; public interface Drink { public float money();//获取价格。 public String description();//返回商品信息。 }
接下来就是装饰者类,继承此接口并通过构造方法获取被装饰对象公共接口:
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(); } }
下面分别是被装饰者:Soya,Suger,Milk,BlackBean,Honey类:
package DecoratorMethod; public class Soya implements Drink{ /** * 具体的装饰者对象,纯豆浆。 */ public String description() { return "纯豆浆"; } public float money() { return 5f; } }
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()); } }
运行结果如下:
如有不妥之处或更好的建议还请小伙伴们不吝赐教!