装饰模式顾名思义就是在不改变原对象的前提下,将新功能优雅的附加(装饰)到该对象上,能够实现对复合算法(策略)的优雅封装、对须要协做的算法(策略)进行有机组合。html
装饰模式和策略模式用法相似,可是也有明显区别——策略模式运行时只能运行一个算法,且其各个算法(算法族)之间必须相互独立,不能有联系,装饰模式没有这些约束。java
装饰模式和策略模式同样,也是对继承的一种替代方案——使用对象组合的方式,作到运行时装饰对象,从而优雅的替代死板的继承。算法
另外,装饰模式是对象的结构型模式。编程
在策略模式:继承、组合和接口用法——策略模式复习总结,里有一个鸭子的案例,说到了继承的种种局限性,这能够用策略模式(组合+接口)改进。一样,咱们也能够用装饰模式。设计模式
看一个新的例子——作手抓饼的例子,如今要实现一个作手抓饼的点餐程序,通常人都会加一个鸡蛋,或者烤肠,辣条等等,加不一样的料,价格确定也不同,下面看代码:安全
public class Cake { // 表明饼 protected String getInfo() { return "这是一个白饼"; } protected double getCost() { return 2.0D; // 卖两元 } }
下面是加了鸡蛋的饼,和加鸡蛋和烤肠的饼架构
public class CakeWithEgg extends Cake { @Override public String getInfo() { return super.getInfo() + "加1鸡蛋"; } @Override public double getCost() { return super.getCost() + 2; } } ////////////////////////////// public class CakeWithEggSausage extends CakeWithEgg { @Override public String getInfo() { return super.getInfo() + "加1个烤肠"; } @Override public double getCost() { return super.getCost() + 2; } }
客户端调用以下:框架
public class Main { public static void main(String[] args) { Cake cake = new Cake(); System.out.println(cake.getInfo() + ",价格:" + cake.getCost()); Cake cake1 = new CakeWithEgg(); System.out.println(cake1.getInfo() + ",价格:" + cake1.getCost()); Cake cake2 = new CakeWithEggSausage(); System.out.println(cake2.getInfo() + ",价格:" + cake2.getCost()); } }
很简单,很快就实现好了,UML 类图以下:ide
看似很正常,很完美,可是此时,有新的需求了——加 N 个鸡蛋或者烤肠等。。。此时会发现,该程序已经没法正常的扩展了,若是仍是依样画葫芦的添加新类,经过继承。。。那么必将致使系统的类“爆炸”——成为垃圾代码的典范案例。。。函数
小结
利用继承扩展类的行为,在代码编译期间就决定了行为是什么了,且全部它的子类都要无脑的接收父类的内容。。。这在某些场景下是不太合理的(联系策略模式里鸭子的例子),而使用组合就能够规避这样的问题。
组合能够动态的组合对象,在不改变现有类的基础上,给这个类添加新的行为,既不会对旧代码引入 bug,也能增长新功能,这就是 OCP(open close principle)——开闭原则的体现。
额外提一句:软件设计在考虑基本原则以前,也要综合考虑 deadline,项目规模,等因素,一句话——屈于现实,有时候容许代码写的不符合设计原则,可是心中要有一条'准线',而不是一直听之任之,甚至随波逐流,无所谓的态度。。。
OCP定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
核心思想:面向抽象编程——用抽象构建框架,用实现扩展细节,其实我以为核心就是一句话,全部的开源框架和设计模式的精髓——加间接层,只要有变化,或者感受别扭,就能够抽象不变的代码,而后加间接层,扩展功能。
OCP 的目的在于面对需求的改变而保持系统的相对稳定,从而使得系统能够很容易的从一个版本升级到另外一个版本。而实际生产环境里,绝对封闭的系统是不存在的。不管模块怎么封闭,到最后总仍是有一些没法封闭的变化。
既然不能作到彻底封闭,那就应该只封闭变化的部分:
一、作粗粒度的隔离:把变化的模块和稳定的模块先区分开
二、作细粒度的隔离:在变化的模块里,尝试对逻辑段落进行封装,把那些实在没法封闭的部分抽象出来,即进行细粒度的隔离——好比使用抽象类,接口等
三、要容许扩展,当系统的变化来临时,要及时的作出反应。而不是一味的就知道修改已有的业务代码
实际上,变化来的越早,抽象就越容易,代码的维护也就越容易
可是,当项目接近于完成时,才来的一些需求,则会使抽象变得很困难。这个困难并非抽象自己的困难,抽象自己不难,难在系统的架构已经完成,修改牵扯的方面太多而使得抽象工做变得很困难。
这是一个矛盾点
总的来讲:
一、不能惧怕改变——当新需求到来时,首先要作的不是上来就修改已有的业务代码,而是尽量的将变化抽象出来进行隔离,而后再进行扩展。
二、面对需求的变化,对程序的修改应该是尽量经过添加代码来实现,而不是经过修改已有代码来实现。固然,现实中,各类状况一综合,你们也都知道,呵呵了。。。
下面采起一些新的设计思路;以饼Cake为主体,咱们认为它是被装饰的实体,如今要实现,在运行时动态的用鸡蛋,烤肠,辣条等装饰者,去装饰饼 cake。全程只 new 一个饼便可,若是客户想要一个加鸡蛋的饼,就用鸡蛋去装饰这个饼,若是想要 N 个鸡蛋,就用 N 个鸡蛋去装饰这个饼,烤肠等同理。最后再调用 cost,info 等方法,将饼的信息和价钱算出来。
其实,装饰理解为包装更合适。联系 Java 的 I/O API,其实就是这么用的,各类 buffer 的流去包装字节,字符流。
如此一来,咱们能够把看似不一样的东西,饼和鸡蛋不一样,看作同一种东西来使用,如何实现呢?天然就用到了组合+委托的设计思想。
既然饼和鸡蛋两个不相干的东西能被当作一个东西来用,它们确定都要继承同一个类型:手抓饼是具体的被装饰的类——Cake,咱们能够给它设计一个抽象的父类,只表明饼——DefaultCake,而鸡蛋是一个具体的装饰类——EggDecorator,它要有一个抽象的(或者非抽象)父类——装饰者类 Decorator。同时咱们让装饰者类 Decorator 也继承 DefaultCake 便可。
下面看代码:
public abstract class DefaultCake { // 表明饼——抽象的饼类,也就是抽象的被装饰类 // 注意,该类也能够用接口实现,且这个角色不是必须的,也能够直接就是具体的被装饰类 protected abstract String getInfo(); protected abstract double getCost(); } ////////////////////////////////////////// public class Cake2 extends DefaultCake { // 具体的被装饰类 @Override protected String getInfo() { return "这是一个白饼"; } @Override protected double getCost() { return 2.0D; } }
如上能解决两个类的类型一致问题,下面让两个类关联起来——使用组合+委托,咱们目的是让装饰者——EggDecorator去装饰被装饰的类——Cake,能够把抽象的被装饰类DefaultCake组合到装饰者Decorator类里,来关联装饰类和被装饰类,而后联系策略模式,经过构造器或者 setter 方法,把抽象的被装饰类DefaultCake注入进装饰类Decorator——这就叫委托,即装饰类Decorator将新的功能,委托给被装饰类DefaultCake去实现。
public class Decorator extends DefaultCake { // 装饰类的父类。这个类也能够设计为抽象的,前提装饰类要额外作一些事情的时候 private DefaultCake defaultCake; // 经过组合抽象的被装饰父类,来关联装饰类和具体的被装饰类 public Decorator(DefaultCake defaultCake) { // 经过构造器注入 // 这样能够把被装饰类DefaultCake传入装饰类Decorator——这也叫委托,即装饰类Decorator将装饰的功能,委托给被装饰类DefaultCake去实现 this.defaultCake = defaultCake; } @Override protected String getInfo() { return this.defaultCake.getInfo(); } @Override protected double getCost() { return this.defaultCake.getCost(); } } ////////////////////////////// public class EggDecorator extends Decorator { // 具体的装饰类——鸡蛋 public EggDecorator(DefaultCake defaultCake) { // 这里必需要实现有参构造器,由于父类写了有参构造器 super(defaultCake); } @Override protected String getInfo() { return super.getInfo() + " 加1个鸡蛋"; } @Override protected double getCost() { return super.getCost() + 2; } } ///////////////////////////// public class SausageDcorator extends Decorator { // 具体的装饰类——烤肠 public SausageDcorator(DefaultCake defaultCake) { super(defaultCake); } @Override protected String getInfo() { return super.getInfo() + " 加1个烤肠"; } @Override protected double getCost() { return super.getCost() + 2; } }
客户端调用:
public class Main2 { public static void main(String[] args) { DefaultCake cake = new Cake2(); cake = new EggDecorator(cake); // 用鸡蛋这个具体的装饰者去包装被装饰者——饼cake,看起来很像 I/O API cake = new EggDecorator(cake); // 再加一个鸡蛋 cake = new EggDecorator(cake); // 理论上能够加 N 个,可是咱们只用一个类就能解决这个需求 cake = new SausageDcorator(cake); // 再加一个烤肠 System.out.println(cake.getInfo() + ",价格:" + cake.getCost()); // 这是一个白饼 加1个鸡蛋 加1个鸡蛋 加1个鸡蛋 加1个烤肠,价格:10.0 } }
其实也不难,下面是类图:
一、装饰者和被装饰者要有相同的超类型
类型的一致性,经过继承共同父类解决
二、装饰者要把装饰的责任委托给被装饰者,这样处理,装饰者能在委托以前或者以后,加上本身的专属行为,以达到特殊目的
三、经过组合+委托,实现动态的,运行时,不限量的为对象增长新功能
四、保持了接口的透明性——OCP 原则
由于装饰者和被装饰者类型具备一致性,因此,它们的 API 也具备一致性,也就实现了接口的透明性,即不会有继承的弊端——覆盖掉父类方法,具体的被装饰类不论被装饰多少次,其父类(或者接口)的 API 都不会被修改,同时这也体现了递归的思想。
再次强调:装饰模式使用继承,是为了实现类型的一致性,而不是为了扩展类的功能。
一、适合替代继承,给类添加新的职责
二、比策略模式更灵活——能够在运行时动态的给一个对象添加新功能,也能够撤销已经添加的新功能,能够同时封装多个算法(策略)去完成一件事。
其实不必说太多了,前面包括策略模式已经说到了不少继承的缺陷,也说到了策略模式的一个缺点——算法族的算法必须独立和平等,装饰模式能够弥补这些缺陷
一、装饰模式比继承灵活,能够在不改变对象的前提下,扩展对象。若是只使用继承扩展类,那么当新功能较多时,会致使类的膨胀和复杂,且子类不必定都要具有父类的这些特性,联系策略模式里鸭子的例子。并且,继承对类的扩展是静态的,在编译时就要肯定,而装饰模式是动态的扩展
二、说到算法族的平等和独立,这是策略模式的局限性,可是装饰模式就能够经过排列组合,实现算法之间的协做和组合,也是题目的意思
增长了系统的类数量,增长了代码量,代码量多了,天然程序就显得复杂一些,可是我认为,瑕不掩瑜,无所谓,能够说是硬给扣的一些帽子,大胆的用吧,若是有人看不懂,只能说对方编程能力比较水,OO 能力比较差。
I/O 包:源码没什么分析的必要了,很直观
还有对集合的线程安全的包装API,好比java.util 包里的 Collections.synchronizedMap()等;