定义
- 在不改变原有对象的基础上,将功能附加到对象上。
- 提供了比继承更有弹性的替代方案(扩展原有对象功能)
类型
结构型
适用场景
装饰者模式在我们生活中也经常会用到,例如我买个蛋糕,我想加些草莓,还想再加些蛋糕,根据不同的需求来装饰这个蛋糕;房子装修也是一样的;还有我们送给朋友礼品,可以选择普通包装、精美包装、包装盒。这些都是装饰者在生活中的使用场景。
那在程序中
- 我们可以扩展一个类的功能或给一个类添加附加职责
- 动态的给一个对象添加功能,这些功能可以再动态的撤销
优点
- 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能
如果需要扩展的功能有繁多,势必会增加很多子类,增加系统的复杂性;同时使用继承实现功能扩展的话,我们必须预见扩展功能,因为这些功能在编译时就确定了,是静态的。
如果使用装饰者模式,这些功能是用户或者应用层的代码来动态的决定加入的方式和时间。装饰者模式提供了一种即插即用的方法,我们可以在运行期间决定何时增加何种功能。
当然,并不是说使用装饰者了就不再使用继承了,因为装饰者也使用了继承。区分下- 继承能达到扩展形式之一,但是不见得能达到弹性设计的最佳方式
- 通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果
打个比方,还是去买蛋糕,我们可以根据不同的排列组合在第一层放什么,第二层放什么,最上层放什么,这些排列组合就可以实现不同的效果了 - 符合开闭原则
如果使用装饰者模式的话,我们可以扩展行为,并且具体的装饰者和被装饰者可以独立变化,原有的代码不用改变,所以符合开闭原则
那如果深入程序来说,其实装饰者做的是把类中的装饰功能从类中移除去,这样简化了杯装饰的类,同时把类的核心职责和装饰功能区分开,这样还可以去除相关类中重复的装饰逻辑
缺点
- 会出现更多的代码,更多的类,增加程序的复杂性
因为我们要抽象,并且还要创建很多装饰者的类 - 动态装饰时,多层装饰时会更复杂
例如说,程序根据用户传入的参数来决定动态的装饰那些类,装饰者程序呢可能比继承使用的类少,但是会比继承关系使用更多的对象,更多的对象呢在我们排查问题时又会变的困难,特别是这些对象又非常像,因为装饰者会继承被装饰对象,被装饰者又有具体的实体,它们在父类上看着是同一类对象,所以在排查错误时增加复杂度,这些都体现在增加复杂性
相关设计模式
- 装饰者模式和代理模式
装饰者模式关注在一个对象上动态的添加方法,而代理模式更在于控制对对象的访问,代理模式的代理对象可以对它的客户隐藏一些具体信息,我们通常在使用代理时在一个代理类中创建一个对象的实例,而当我们使用装饰者模式时,我们通常会把原始对象作为一个参数传给装饰者的构造器,这个是使用上的一些不同 - 装饰者模式和适配器模式
两者都可以叫做包装模式wrapper,装饰者和被装饰者可以实现相同的接口,或者装饰者是被装饰者的子类;在适配器模式中,适配器和被适配的类有不同的接口,当然也有可能有部分的接口是重合的。如果再往深入讲解的话,装饰者模式还可以退化成半装饰者,也就是说一个装饰者除了提供被装饰类的接口外,还提供了其它的方法,那就变成了一个半透明的装饰者。如果我们应用层代码想使用提供了特殊的方法,就要使用具体的装饰者类了,半装饰者在实际的业务场景中使用的比较少,我们重点关注装饰者
coding
不使用装饰者模式用继承
有一个场景,上班时候在楼下有卖煎饼的,买个煎饼然后去公司茶水间吃煎饼。煎饼有时候加个蛋,有时候加根肠,那我们现在就想一想,商家卖这个煎饼加蛋的时候加香肠的时候价格是怎么计算的?
首先创建一个装饰者包
com.design.pattern.creational.structural.decorator
再搞一个v1版本的
package com.design.pattern.creational.structural.decorator.v1;
/**
* 煎饼
**/
public class Battercake {
protected String getDes(){
return "煎饼";
}
protected int cost(){
return 8;
}
}
package com.design.pattern.creational.structural.decorator.v1;
/**
* 加蛋的煎饼
**/
public class BattercakeWithEgg extends Battercake{
@Override
public String getDes() {
return super.getDes()+"加一个鸡蛋";
}
@Override
public int cost() {
return super.cost()+1;
}
}
package com.design.pattern.creational.structural.decorator.v1;
/**
* 煎饼加一个鸡蛋加一根香肠
**/
public class BattercakeWithEggSausage extends BattercakeWithEgg{
@Override
public String getDes() {
return super.getDes()+"加一根香肠";
}
@Override
public int cost() {
return super.cost()+2;
}
}
package com.design.pattern.creational.structural.decorator.v1;
/**
* 测试类
**/
public class Test {
public static void main(String[] args) {
//煎饼
Battercake battercake=new Battercake();
System.out.println(battercake.getDes()+" 销售价格:"+battercake.cost());
//煎饼加一个鸡蛋
Battercake battercakeWithEgg=new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getDes()+" 销售价格:"+battercakeWithEgg.cost());
//一个煎饼加一个鸡蛋加一根香肠
Battercake battercakeWithEggSausage=new BattercakeWithEggSausage();
System.out.println(battercakeWithEggSausage.getDes()+" 销售价格:"+battercakeWithEggSausage.cost());
}
}
运行结果
煎饼 销售价格:8
煎饼加一个鸡蛋 销售价格:9
煎饼加一个鸡蛋加一根香肠 销售价格:11
看到三个人都可以满足需求了,但是这时候来了一个客户,说“我只要吃一个煎饼,加2个鸡蛋2根香肠”,这时候老板有点迷茫,我没有创建加2个加蛋加2根香肠的类呀,现在这个系统是算不出来卖多少钱的,所以这个小伙没有得到满足,商家呢也没赚到钱,也没卖出去,因为系统不支持
看下上面的类图
看到这个扩展性还是非常有限制的,我们想一下对于煎饼的组合会非常非常的多,加一个加蛋加2个鸡蛋加10个加蛋的都有,下面的子类会不会发生来爆炸的情况呢?如果现在的这种写法来扩展的话肯定会发生的。
使用装饰者模式
怎么让代码更优雅,我们创建一个v2版本,这时候装饰者模式正式登场
现在我们要对煎饼进行抽象,我们要有抽象的实体类,还要有确定的实体类,同时需要有抽象的装饰者,还要有确定的装饰者,在这个场景中被装饰的是煎饼,装饰者是鸡蛋还有香肠,在装饰者模式中四个角色还是都有的
抽象的煎饼
/**
* 抽象的煎饼
**/
public abstract class AbstractBattercake {
protected abstract String getDes();
protected abstract int cost();
}
实体的煎饼
/**
* 煎饼
**/
public class Battercake extends AbstractBattercake{
@Override
protected String getDes(){
return "煎饼";
}
@Override
protected int cost(){
return 8;
}
}
抽象的装饰者
package com.design.pattern.creational.structural.decorator.v2;
/**
* 抽象的装饰者
**/
public class AbstractDecorator extends AbstractBattercake {
private AbstractBattercake abstractBattercake;
public AbstractDecorator(AbstractBattercake abstractBattercake) {
this.abstractBattercake = abstractBattercake;
}
@Override
protected String getDes() {
return this.abstractBattercake.getDes();
}
@Override
protected int cost() {
return this.abstractBattercake.cost();
}
}
鸡蛋装饰者
package com.design.pattern.creational.structural.decorator.v2;
/**
* 鸡蛋装饰者
**/
public class EggDecorator extends AbstractDecorator{
public EggDecorator(AbstractBattercake abstractBattercake) {
super(abstractBattercake);
}
@Override
protected String getDes() {
return super.getDes()+" 加一个鸡蛋";
}
@Override
protected int cost() {
return super.cost()+1;
}
}
香肠装饰者
package com.design.pattern.creational.structural.decorator.v2;
/**
*香肠装饰者
**/
public class SausageDecorator extends AbstractDecorator {
public SausageDecorator(AbstractBattercake abstractBattercake) {
super(abstractBattercake);
}
@Override
protected String getDes() {
return super.getDes()+" 加一根香肠";
}
@Override
protected int cost() {
return super.cost()+2;
}
}
最上面的抽象煎饼,可以是一个接口也可以是一个抽象类,但是这个抽象角色并不是必须的
Battercake就是具体的被装饰的煎饼,这个煎饼也可以有多个
AbstractDecorator就是抽象的装饰者
下面的两个就是鸡蛋和香肠装饰者,负责给煎饼扩展功能
我们接下来看看如何使用
package com.design.pattern.creational.structural.decorator.v2;
/**
* 应用类
**/
public class Test {
public static void main(String[] args) {
AbstractBattercake abstractBattercake;
//煎饼
abstractBattercake=new Battercake();
//加一个鸡蛋
abstractBattercake=new EggDecorator(abstractBattercake);
//再加一个鸡蛋
abstractBattercake=new EggDecorator(abstractBattercake);
//加一根香肠
abstractBattercake=new SausageDecorator(abstractBattercake);
System.out.println(abstractBattercake.getDes()+" 销售价格:"+abstractBattercake.cost());
}
}
运行结果
煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 销售价格:12
这样就通过装饰者把煎饼装饰起来了。
我们再看下抽象的装饰者类 public class AbstractDecorator extends AbstractBattercake {
,这个类并不是抽象类,但是也能完成扩展的目的,为什么要加一层抽象的类呢?很简单,我们把AbstractBattercake 改成抽象的类,并写一个doSomething的方法
package com.design.pattern.creational.structural.decorator.v2;
/**
* 抽象的装饰者
**/
public abstract class AbstractDecorator extends AbstractBattercake {
private AbstractBattercake abstractBattercake;
public AbstractDecorator(AbstractBattercake abstractBattercake) {
this.abstractBattercake = abstractBattercake;
}
protected abstract void doSomething();
@Override
protected String getDes() {
return this.abstractBattercake.getDes();
}
@Override
protected int cost() {
return this.abstractBattercake.cost();
}
}
我们想象一下,如果作为抽象的装饰者,能保证子类能实现某个方法的话,那么这个抽象的装饰者放在这里,这个类是不是抽象的类,还要看业务场景,像上面的煎饼例子不是抽象的装饰者也是ok的,因为并没有抽象方法doSomething。如果加蛋和加香肠的时候,需要特定的小动作,这两个小动作分别属于各自的装饰者实现,这个时候对于两个实体的装饰者父类,使用抽象的类比较合适。
装饰者在实际的业务场景用的比较多,当然怎么用还是要看业务模型的,如果业务模型抽象程度可以的话,打个比方,假设有个订单,而这个订单是个旅行订单,现在推出了旅行保险,我们想象一下是否可以用保险再装饰一下订单呢?就跟鸡蛋装饰煎饼一样,当然这种情况还是要看实际的业务模型,还有对接难度等等,这里只是打个比方。
源码解析
JDK中BufferedReader
package java.io;
public class BufferedReader extends Reader {
private Reader in;
BufferedReader 把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];
nextChar = nChars = 0;
}
通过Reader构造BufferedReader。JDK中的java.io采用了装饰者模式,我们可以无限次的进行装饰转换,转换的目标就是得到我们想要的数据类型的流对象
JDK中BufferedInputStream
public
class BufferedInputStream extends FilterInputStream {
BufferedInputStream父类FilterInputStream装饰者模式
public
class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
我们看下UML
FilterInputStream就是一个装饰者,下面的都是实际的装饰者。在Reader和Writer流中也是一样的,大同小异,我们在使用IO流的时候,因为类的数量比较多,有时候我们也记不住,但是如果我们能识别出那些是抽象被装饰,那些是实际被装饰者,那些是抽象装饰者,那些是实体装饰者,其实对我们理解来说是非常有益处的。上面就是装饰者在JDK中的一些应用
spring中的TransactionAwareCacheDecorator
package org.springframework.cache.transaction;
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
从名字可以看出来,这个类是处理spring缓存和同步事务的相关类,那我们从类里面可以看出来它实现类cache,并把Cache组装进来,因此我们可以说它是一个Cache的装饰者,还有类名也是非常明显,还有构造器也很明显
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
spring中SessionRepositoryRequestWrapper
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private Boolean requestedSessionIdValid;
private boolean requestedSessionInvalidated;
private final HttpServletResponse response;
private final ServletContext servletContext;
private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {
super(request);
this.response = response;
this.servletContext = servletContext;
}
看到继承了HttpServletRequestWrapper ,HttpServletRequestWrapper 实现了HttpServletRequest
public class HttpServletRequestWrapper extends ServletRequestWrapper implements
HttpServletRequest {
而构建的自己的时候传入了一个HttpServletRequest,那HttpServletRequest和SessionRepositoryRequestWrapper什么关系呢?
看下UML
再看类图,可以发现SessionRepositoryRequestWrapper 装饰了HttpServletRequest
上面uml类图中上部分就开始了装饰者
mybatis中Cache
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
这是一个接口,它位于哪里?
先看下FifoCache,先进先出算法
public class FifoCache implements Cache {
private final Cache delegate;
private final Deque<Object> keyList;
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList();
this.size = 1024;
}
delegate(委托)命名还是非常好的,FifoCache 里面的操作都是委托Cache实现的,只不过里面包装了下,private final Deque keyList;
还有LruCache,这个小伙伴应该比较熟悉
/**
* Lru (least recently used) cache decorator.
*
* @author Clinton Begin
*/
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
看这里的源码发现命名非常精准,注释也非常容易理解,这点是非常值得我们学习的。
从名字我们看一下ScheduledCache
是调度缓存方面的内容,这个是SerializedCache
序列化和反序列化方面的内容,SoftCache
软引用,还有SynchronizedCache
,防止多线程并发访问,里面方法都是有synchronized修饰的,很容易理解,只是在Cache上面包装了一层,并且都加了同步方法。TransactionalCache
事务相关的缓存,有兴趣的同学非常建议把MyBatis这些相关的类看一遍,这个包下都是对Cache的一些装饰,重点围绕Cache。
刚刚看了JDK的spring中的springSession中的还有Mybatis中的Cache关于装饰者模式的使用,那装饰者模式在源码框架中应用的如此广泛,包括我们使用的JDK,所以装饰者模式是非常值得好好学习的一个设计模式。
也希望能好好学习这个模式,同时能触类旁通,在我们自己的业务中来抽象出来可以使用装饰者模式的一些业务模型。当然我们不是为了使用设计模式而使用设计模式,一定是找到对应的场景的时候才来使用它。