Java设计模式之装饰器模式

本文由老王将建好的书房计划请小王来帮忙,小王却想谋权篡位,老王通过教育他引出装饰器设计模式,第二部分针对老王提出的建设性意见实现装饰器模式,第三部分针对装饰器模式在Jdk中的IO、Spring中的缓存管理器、Mybatis的运用来加强我们的理解,第四部分说明装饰器模式和代理模式的区别及他们各自的应用场景。

读者可以拉取完整代码到本地进行学习,实现代码均测试通过后上传到码云,本地源码下载。

一、引出问题

上篇文章(Java设计模式之组合模式)对老王的书架改造以后,老王是相当的满意,看小王能力突出,这不老王又有了新的需求。

经过组合模式以后老王的书被管理的井井有条,但是随着书的增多,老王就有一些忙不过来了,老王就想让小王帮他处理一些额外的事,比如在买书之前打扫一下书房,在晚上的时候把书房的门锁一下;或者有人借书之前做一下记录,借书者还书以后小王接收一下,等等。

小王听完说这有何难,说完撸起袖子就准备改老王的代码。老王急忙拦住了他,你真是个呆瓜,我写的代码你凭什么要动,你改了会不会影响我的业务逻辑,平时让你多看书你不听,之前学的设计模式呢?不拿出来用,眼看着让他吃灰。

小王不好意思的挠挠头,翻出来了他的设计模式宝典,开始寻找合适的设计模式。

小王大喊有了,之前说过的代理模式可以很好的解决这个问题,代理模式可以动态的增强对象的一些特性,我准备使用代理模式完成这个需求。

老王听完止不住的摇摇头,看来你是打算谋权篡位了,你是想要我整个书房的权利呀!

老王解释说,代理模式是可以实现这个需求,但是在这个场景下显然代理模式不合适,代理模式是着重对对象的控制,而我们今天的需求是在该对象的基础之上增加他的一些功能,我们各自的业务独立发展互不干扰。

二、装饰器模式概念与使用

实际上,在原对象的基础之上增加其功能就是属于装饰器模式。

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

在装饰器模式中应该是有四个角色:

①Component抽象构件(老王抽象方法)
②ConcreteComponent 具体构件(老王实现方法)
③Decorator装饰角色(装饰者小王)
④ConcreteDecorator 具体装饰角色(装饰者小王实现方法)

在装饰器模式中,需要增强的类(被装饰者)要实现接口,装饰者继承被装饰者的接口,并将被装饰者的实例传进去,在具体装饰角色中调用被装饰者的方法,在其前后定义增强的方法,在实际应用中往往装饰角色和具体装饰角色合二为一。

我们看下具体的代码实现:

抽象构件:

/**
 * 书的抽象构件
 * @author tcy
 * @Date 10-08-2022
 */
public abstract class ComponentBook {

    /**
     * 借书
     */
    public abstract void borrowBook();

    /**
     * 买书
     */
    public abstract void buyBook();

}

书的具体构件:

/**
 * 书的具体构件
 * @author tcy
 * @Date 10-08-2022
 */
public class ConcreteComponentBook extends ComponentBook{
    @Override
    public void borrowBook() {
        System.out.println("老王的书借出去...");

    }

    @Override
    public void buyBook() {
        System.out.println("老王的书买回来...");

    }
}

装饰角色:

/**
 * 书的装饰者
 * @author tcy
 * @Date 10-08-2022
 */
public class DecoratorBook extends ComponentBook{

    private ComponentBook componentBook;

    DecoratorBook(ComponentBook componentBook){
        this.componentBook=componentBook;
    }

    @Override
    public void borrowBook() {
        this.componentBook.borrowBook();
    }

    @Override
    public void buyBook() {
        this.componentBook.buyBook();
    }
}

书的具体装饰角色:

/**
 * 子类里写了并且使用了无参的构造方法但是它的父类(祖先)中却至少有一个是没有无参构造方法的
 * @author tcy
 * @Date 10-08-2022
 */
public class ConcreteDecoratorBook1 extends DecoratorBook{

    ConcreteDecoratorBook1(ComponentBook componentBook) {
        super(componentBook);
    }

    public void cleanRoom(){
        System.out.println("打扫书房...");
    }

    public void shutRoom(){
        System.out.println("关闭书房...");
    }

    public void recordBook(){
        System.out.println("记录借出记录...");
    }

    public void returnBook(){
        System.out.println("收到借出去的书...");
    }

    @Override
    public void buyBook() {
        this.cleanRoom();
        super.buyBook();
        this.shutRoom();
        System.out.println("----------------------------");
    }

    @Override
    public void borrowBook() {
        this.recordBook();
        super.borrowBook();
        this.returnBook();
        System.out.println("----------------------------");
    }
}

如果读者的Java基础扎实,理解装饰器还是比较轻松的,装饰器的实现方式很直观,需要特别指出的是,在书的具体装饰角色中,要显示的定义一个构造方法。

基础不太扎实的读者可能会有一个疑问,在Java的类中默认不是会有一个无参的构造方法吗?为什么这里还需要定义呢?

在java中一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建。

也就是说当你用子类的无参构造函数创建子类对象时,会去先递归调用父类的无参构造方法,这时候如果某个类的父类没有无参构造方法就会编译出差。所以我们在子类中可以手动定义一个无参方法,或者在父类中显示的定义一个构造方法。

客户端:

/**
 * @author tcy
 * @Date 09-08-2022
 */
public class  {
    public static void main(String[] args) {

        ComponentBook componentBook=new ConcreteComponentBook();
        componentBook=new ConcreteDecoratorBook1(componentBook);

        componentBook.borrowBook();

        componentBook.buyBook();
    }
}

方法调用后我们可以看到执行结果:

记录借出记录...
老王的书借出去...
收到借出去的书...
----------------------------
打扫书房...
老王的书买回来...
关闭书房...
----------------------------

在老王借书和买书的这个事件中,成功的织入进去小王的方法,这样也就实现了装饰器模式。

为了加强理解我们接着看装饰器模式在我们经常接触的源码中的运用。

三、应用

1、jdk中的应用IO

装饰器在java中最典型的应用就是IO,我们知道在IO家族中有各种各样的流,而流往往都是作用在子类之上,然后增加其附加功能,我们以InputStream 举例。

InputStream 是字节输入流,此抽象类是表示字节输入流的所有类的超类。

FileInputStream是InputStream 的一个实现父类,BufferedInputStream是FileInputStream的实现父类。

实际BufferedInputStream就是装饰者,InputStream 就是抽象构件,FileInputStream是具体构件,BufferedInputStream就是对FileInputStream进行了包装。

我们看具体的应用:

FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

直接将FileInputStream的实例传给BufferedInputStream的构造方法,就能调用BufferedInputStream增强的一些方法了。

我们具体看BufferedInputStream装饰器类:

public class BufferedInputStream extends FilterInputStream {

    private static int DEFAULT_BUFFER_SIZE = 8192;

    protected int marklimit;

    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

  	...
    //增强的方法
    private void fill() throws IOException {
        byte[] buffer = getBufIfOpen();
        if (markpos < 0)
            pos = 0;            /* no mark: throw away the buffer */
        else if (pos >= buffer.length)  /* no room left in buffer */
            if (markpos > 0) {  /* can throw away early part of the buffer */
                int sz = pos - markpos;
                System.arraycopy(buffer, markpos, buffer, 0, sz);
                pos = sz;
                markpos = 0;
            } else if (buffer.length >= marklimit) {
                markpos = -1;   /* buffer got too big, invalidate mark */
                pos = 0;        /* drop buffer contents */
            } else if (buffer.length >= MAX_BUFFER_SIZE) {
                throw new OutOfMemoryError("Required array size too large");
            } else {            /* grow buffer */
                int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                        pos * 2 : MAX_BUFFER_SIZE;
                if (nsz > marklimit)
                    nsz = marklimit;
                byte nbuf[] = new byte[nsz];
                System.arraycopy(buffer, 0, nbuf, 0, pos);
                if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                    // Can't replace buf if there was an async close.
                    // Note: This would need to be changed if fill()
                    // is ever made accessible to multiple threads.
                    // But for now, the only way CAS can fail is via close.
                    // assert buf == null;
                    throw new IOException("Stream closed");
                }
                buffer = nbuf;
            }
        count = pos;
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
        if (n > 0)
            count = n + pos;
    }

   ...

BufferedInputStream类中有许多其他的方法,就是对FileInputStream类的增强。

2、Spring中的运用

Spring使用装饰器模式有两个典型的特征,一个是类名中含有Wrapper,另一类是含有Decorator,功能也即动态的给某些类增加一些额外的功能。

TransactionAwareCacheDecorator是处理spring有事务的时候缓存的类,我们在使用spring的cache注解实现缓存的时候,当出现事务的时候,那么缓存的同步性就需要做相应的处理了,于是就有了这个装饰者。

public class TransactionAwareCacheDecorator implements Cache {

	//抽象构件
   private final Cache targetCache;

   /**
    * Create a new TransactionAwareCache for the given target Cache.
    * @param targetCache the target Cache to decorate
    */
   public TransactionAwareCacheDecorator(Cache targetCache) {
      Assert.notNull(targetCache, "Target Cache must not be null");
      this.targetCache = targetCache;
   }

   /**直接调用未增强
    * Return the target Cache that this Cache should delegate to.
    */
   public Cache getTargetCache() {
      return this.targetCache;
   }

	//直接调用未增强
   @Override
   public String getName() {
      return this.targetCache.getName();
   }

//直接调用未增强
   @Override
   public Object getNativeCache() {
      return this.targetCache.getNativeCache();
   }

//直接调用未增强
   @Override
   @Nullable
   public ValueWrapper get(Object key) {
      return this.targetCache.get(key);
   }

//直接调用未增强
   @Override
   public <T> T get(Object key, @Nullable Class<T> type) {
      return this.targetCache.get(key, type);
   }

//直接调用未增强
   @Override
   @Nullable
   public <T> T get(Object key, Callable<T> valueLoader) {
      return this.targetCache.get(key, valueLoader);
   }

//先进行判断确定是否需要增强
   @Override
   public void put(final Object key, @Nullable final Object value) {
      if (TransactionSynchronizationManager.isSynchronizationActive()) {
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
               TransactionAwareCacheDecorator.this.targetCache.put(key, value);
            }
         });
      }
      else {
         this.targetCache.put(key, value);
      }
   }
}

Cache是抽象构件,TransactionAwareCacheDecorator就是装饰者,而Cache的实现类就是具体构件。

因为并非所有的方法都会使用事务,有的普通方法就不需要装饰,有的就需要,所以就使用了装饰者模式来完成。

比如put()方法:

  @Override
   public void put(final Object key, @Nullable final Object value) {
      if (TransactionSynchronizationManager.isSynchronizationActive()) {
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
               TransactionAwareCacheDecorator.this.targetCache.put(key, value);
            }
         });
      }
      else {
         this.targetCache.put(key, value);
      }
   }

会在put前判断是否开启了事务TransactionSynchronizationManager.isSynchronizationActive(),如果开启事务就调用一下额外的方法,如果没有开始事务就调用默认的方法。

我们举的这个例子调用的就是 TransactionSynchronizationManager.registerSynchronization()方法,也即是为当前线程注册一个新的事务同步。

在Spring中将装饰角色和具体装饰角色合二为一,直接在装饰者中实现要增加的方法。

3、MyBatista的运用

了解过MyBatis的大致执行流程的读者应该知道,Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是合适的。

我们首先看下Executor类的继承结构。

我们将关键的CachingExecutor代码放上:

public class CachingExecutor implements Executor {
    //持有组件对象
  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();
    //构造方法,传入组件对象
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
      //转发请求给组件对象,可以在转发前后执行一些附加动作
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  //...
 }

Executor就是抽象构件,BaseExecutor是具体构件的实现,CachingExecutor就是装饰角色,那具体装饰角色在哪呢?

实际中具体装饰角色直接在装饰角色中集成了,并没有将具体装饰角色完全独立出来。

另外,Mybatis的一级缓存和二级缓存也是使用的装饰者模式,有兴趣的读者可以拉取Mybatis的源代码本地进行调试研究

四、总结

到此为止,我们就将装饰器模式的内容讲解清楚了,看到这读者可能发现,针对某一类需求可能会有很多设计模式都能完成需求,但一定是有最合适的那一个,就像我们今天举的例子无论是用装饰器模式还是代理模式都可以实现这个需求。

但我们看代理模式中我们列举的例子是以租房做例子,中介将房子的权利完全移交过去,中介完全控制房子做一些改造,今天书房的需求只是让小王来帮忙的,还是以老王为主体,小王只是做一些附加。

装饰器模式就是在瓶里面插了一朵花,而代理模式是把瓶子都给人家了,让人家随便折腾。

如果我们的需求是日志收集、拦截器,代理模式是最适合的。如果是动态的增加对象的功能、限制对象的执行条件、参数控制和检查等使用适配器模式就更加合适了。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • Java设计模式以虹猫蓝兔的故事讲解装饰器模式

    目录 什么是装饰器模式 优点 缺点 知识点 装饰器模式实现 七侠 虹猫 加料 加盐 加孜然 测试 总结 模式: 装饰器模式 案例: 黑小虎抓住了七侠,把虹猫烤了 什么是装饰器模式 装饰器(Decorator)模式的定义: 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式. 优点 1.装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用 2.通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效

  • Java结构性设计模式中的装饰器模式介绍使用

    目录 装饰器模式 概述 实现原理 主要角色 应用场景 优缺点 装饰器模式的基本使用 创建抽象组件 具体组件 抽象装饰器 具体装饰器 客户端调用 装饰器模式 概述 装饰器模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),属于结构型模式. 它是指在不改变原有对象的基础之上,允许向一个现有的对象添加新的功能,同时又不改变其结构,作为现有的类的一个包装. 这种模式创建一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能. 提供了比继承

  • java设计模式之装饰器模式(Decorator)

    概述 装饰模式是对客户端以透明的方式扩展对象的功能,是继承关系的一个替代方案.也就是说,客户端并不会觉得对象在装饰前和装饰后有什么不同,装饰模式可以在不用创造更多子类的情况下,将对象的功能加以扩展,装饰模式的关键在于这种扩展是完全透明的. 模式的结构 UML类图: 装饰模式中的类角色: 抽象构件角色(Project):给出一个接口,以规范准备接收附加责任的对象 具体构件角色(Employe):定义一个将要接收附加责任的类 装饰角色(Manager):持有一个构件对象的实例,并定义一个与抽象构件接

  • Java中常用的设计模式之装饰器模式详解

    目录 优点 缺点 使用场景 一.实现方式 二.测试 总结 优点 1.装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能. 缺点 2.多层装饰比较复杂. 使用场景 1.扩展一个类的功能. 2.动态增加功能,动态撤销. 一.实现方式 假设一个场景,我们房间每天起床都要刷牙, 睡觉也要刷牙,刷牙的动作就是一个装饰器的作用,这样更利于我们的口腔健康.接下来我们就看看具体的装饰器如何实现. 1.每天生活的接口 package com.asurpl

  • 浅谈Java设计模式系列-装饰器模式

    一.概述 装饰器模式作用是针对目标方法进行增强,提供新的功能或者额外的功能. 不同于适配器模式和桥接模式,装饰器模式涉及的是单方,和代理模式相同,而且目标必须是抽象的. 而实际上,装饰器模式和代理模式的实现方式基本一致,只在目标的存在上有些差别,这个后面我们具体讲述. 二.初步分析 上面提到了两点: 涉及的是单方 目标是抽象的 我们来想一下,所谓单方主要指的是在整个装饰器模式中不存在双方调用,要解决的也不是双方调用的问题,而是解决单方提供对外服务的问题,这个单方在自行对外提供服务时,功能不足,或

  • Java设计模式之装饰器模式

    本文由老王将建好的书房计划请小王来帮忙,小王却想谋权篡位,老王通过教育他引出装饰器设计模式,第二部分针对老王提出的建设性意见实现装饰器模式,第三部分针对装饰器模式在Jdk中的IO.Spring中的缓存管理器.Mybatis的运用来加强我们的理解,第四部分说明装饰器模式和代理模式的区别及他们各自的应用场景. 读者可以拉取完整代码到本地进行学习,实现代码均测试通过后上传到码云,本地源码下载. 一.引出问题 上篇文章(Java设计模式之组合模式)对老王的书架改造以后,老王是相当的满意,看小王能力突出,

  • PHP设计模式之装饰器模式定义与用法详解

    本文实例讲述了PHP设计模式之装饰器模式定义与用法.分享给大家供大家参考,具体如下: 什么是装饰器模式 作为一种结构型模式, 装饰器(Decorator)模式就是对一个已有结构增加"装饰". 适配器模式, 是为现在有结构增加的是一个适配器类,.将一个类的接口,转换成客户期望的另外一个接口.适配器让原本接口不兼容的类可以很好的合作. 装饰器模式是将一个对象包装起来以增强新的行为和责任.装饰器也称为包装器(类似于适配器) 有些设计设计模式包含一个抽象类,而且该抽象类还继承了另一个抽象类,这

  • PHP设计模式之装饰器模式实例详解

    本文实例讲述了PHP设计模式之装饰器模式.分享给大家供大家参考,具体如下: 装饰器模式又叫装饰者模式.装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. UML类图: 角色: 组件对象的接口:可以给这些对象动态的添加职责 所有装饰器的父类:需要定义一个与组件接口一致的接口,并持有一个Component对象,该对象其实就是被装饰的对象. 具体的装饰器类:实现具体要向被装饰对象添加的功能.用来装饰具体的组件对象或者另外一个

  • PHP设计模式之装饰器模式定义与用法简单示例

    本文实例讲述了PHP设计模式之装饰器模式定义与用法.分享给大家供大家参考,具体如下: 装饰器模式: 如果已有对象的部分内容或功能性发生改变,但是不需要修改原始对象的结构或不使用继承,动态的扩展一个对象的功能,则应该使用装饰器模式. 简单点说:就是我们不应该去修改已有的类,而是通过创建另外一个装饰器类,通过这个装饰器类去动态的扩展其需要修改的内容,这样做的好处就是---- 1.我们可以保证类的层次不会因过多而发生混乱. 2.当我们需求的修改很小时,不用改变原有的数据结构. 代码引用自<PHP设计模

  • python设计模式之装饰器模式

    目录 装饰器模式 python 装饰器语法糖 装饰器模式 装饰器模式解决什么问题? 增加新功能,且不改变原有功能和代码 选配,根据不同情况,动态调整功能 装饰器,顾名思义,就是用来装饰其他实物,增加被装饰物的功能而不改变被装饰物.这就像我们买车,同一款车型,有很多功能是选配的,但是车的核心功能不变.我们可以在购买时,根据需要选配不同的配置. 如何实现? 想象一下,你是一个装修工,如果要装饰一辆车,那么你首先需要有一辆车,而且在装饰过程中,你不能够改变车原有的功能(接口). 如类图所示: 核心功能

  • Java设计模式之装饰者模式详解

    目录 具体代码: Person: Student: Doctor: DecoratePerson: ShoeDecorate: DressDecorate: 总结 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 以一个Person对象为例.Person作为一个接口,Student(学生)和Doctor(医生)为Person接口的两个具体类,DecoratorPerson为Pers

  • C#设计模式之装饰器模式实例详解

    最近踢了场球,9人制比赛,上半场我们采用防守阵型效果不佳,下半场采用进攻阵型取得了比赛的主动.我们上下半场所采取的策略,似乎可以用"装饰器"模式实现一遍. 首先肯定是抽象基类. public abstract class OurStrategy { public abstract void Play(string msg); } 通常,在上半场,我们一般都使用防守阵型. public class OurDefaultStategy : OurStrategy { public over

随机推荐