前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >设计模式之装饰器模式

设计模式之装饰器模式

原创
作者头像
程序员田同学
发布于 2022-08-10 07:07:09
发布于 2022-08-10 07:07:09
25700
代码可运行
举报
文章被收录于专栏:java-springjava-spring
运行总次数:0
代码可运行

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

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

一、引出问题

上篇文章对老王的书架改造以后,老王是相当的满意,看小王能力突出,这不老王又有了新的需求。

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

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

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

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

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

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

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

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

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

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

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

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

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

抽象构件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 书的抽象构件
 * @author tcy
 * @Date 10-08-2022
 */
public abstract class ComponentBook {/**
     * 借书
     */
    public abstract void borrowBook();/**
     * 买书
     */
    public abstract void buyBook();}

书的具体构件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 书的具体构件
 * @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("老王的书买回来...");}
}

装饰角色:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 书的装饰者
 * @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();
    }
}

书的具体装饰角色:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 子类里写了并且使用了无参的构造方法但是它的父类(祖先)中却至少有一个是没有无参构造方法的
 * @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中一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建。

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

客户端:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @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();
    }
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
记录借出记录...
老王的书借出去...
收到借出去的书...
----------------------------
打扫书房...
老王的书买回来...
关闭书房...
----------------------------

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

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

三、应用

1、jdk中的应用IO

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

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

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

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

我们看具体的应用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FileInputStream fileInputStream = new FileInputStream(filePath); 
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

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

我们具体看BufferedInputStream装饰器类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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注解实现缓存的时候,当出现事务的时候,那么缓存的同步性就需要做相应的处理了,于是就有了这个装饰者。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @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代码放上:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的源代码本地进行调试研究

四、总结

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

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

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

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

推荐读者,参考软件设计七大原则 认真阅读往期的文章,认真体会。

创建型设计模式

一、设计模式之工厂方法和抽象工厂

二、设计模式之单例和原型

三、设计模式之建造者模式

结构型设计模式

四、设计模式之代理模式

五、设计模式之适配器模式

六、桥接模式

七、组合模式

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
3年工作必备 装饰器模式
今天我给大家分享设计模式中的装饰器模式。用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式。
田维常
2021/06/09
3650
3年工作必备  装饰器模式
设计模式 | 装饰者模式及典型应用
装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
小旋锋
2019/01/21
3960
设计模式之命令模式
本文通过解决老王经常搞错借书人的问题,来引出行为型模式中的命令模式。为了在案例之上理解的更加透彻,我们需要了解命令模式在源码中的应用。最后指出命令模式的应用场景和优缺点。
程序员田同学
2022/08/30
2470
设计模式之命令模式
设计模式学习笔记(十)装饰器模式及其应用
装饰器(Decorator)模式:指不改变现有对象结构的情况下,动态地给该对象增加额外功能。
归思君
2023/10/16
3560
设计模式学习笔记(十)装饰器模式及其应用
设计模式 | 结构型 | 装饰器模式
装饰模式是一种结构型设计模式,允许通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
被水淹没
2023/02/25
2790
设计模式 | 结构型 | 装饰器模式
设计模式—— 十七:装饰器模式
● Component(抽象构件) 抽象构件它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法。
三分恶
2020/07/16
1.1K0
从源码角度理解Java设计模式——装饰者模式
优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个功能按想要的顺序执行,以实现不同效果。
我叫刘半仙
2019/03/12
1K0
从源码角度理解Java设计模式——装饰者模式
详解设计模式:装饰器模式
装饰器模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),是 GoF 的 23 种设计模式中的一种结构型设计模式。
栗筝i
2022/12/02
4250
详解设计模式:装饰器模式
Java中的设计模式(三):装饰器模式
  上周心血来潮去了一趟小鹏的体验店,一进门,销售的小哥就开始给我介绍各种不同配置的车,什么智享版、智尊版,听得我是头晕脑胀,赶紧告辞。
闲宇非鱼
2022/02/08
5560
Java中的设计模式(三):装饰器模式
【设计模式】之装饰器模式
在实际生产中,某个类的行为(它所提供的方法)已经没法满足当前的需要了,但是又需要使用原有的部分功能,因此需要对原有对象进行增强——装饰器设计模式(Decorator Pattern)也叫包装器模式就是为解决此问题而诞生的,它是对原有类的一个包装,属于结构性设计模式。
青山师
2023/05/05
2010
【设计模式】之装饰器模式
装饰器模式(Decorator)
装饰器模式(Decorator) 对客户透明的方式动态地给一个对象附加上更多的责任,同时又不改变其结构。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。 类图: 1.抽象构件(Comp
qubianzhong
2019/07/01
4480
设计模式之适配器模式
本文通过老王使用纸质书籍阅读小王使用电子书籍的故事,详细说明设计模式中的结构型设计模式之适配器模式,分别对对象适配器和类适配器代码实现,最后为了加深理解,会列举适配器设计模式在JDK和Spring源码中的应用。
程序员田同学
2022/08/04
3690
设计模式--装饰者模式思考
装饰者模式实际上是一直提倡的组合代替继承的实践方式,个人认为要理解装饰者模式首先需要理解为什么需要组合代替继承,继承又是为什么让人深恶痛绝.
屈定
2018/09/27
9710
设计模式--装饰者模式思考
【设计模式自习室】装饰模式
该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。
蛮三刀酱
2020/01/14
4620
设计模式【8】-- 手工耿教我写装饰器模式
装饰器模式,属于结构型模式,用来包裹封装现在的类对象,希望可以在不修改现在类对象和类定义的前提下,能够拓展对象的功能。
秦怀杂货店
2022/01/06
2750
设计模式之责任链模式
本文通过图书馆管理系统中,用户名校验、密码校验、需要增加问题,每次都要增加if判断语句,将其改用责任链模式进行链式调用,为了让代码更加的优雅,我们使用之前学过的建造者模式就代码进行改造。接着我们会介绍责任链模式在我们常用的框架中的运用,最后是责任链模式的优缺点和应用场景。
程序员田同学
2022/08/24
3510
设计模式之责任链模式
【Java设计模式】014-装饰器模式
装饰器(Decorator)模式:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
訾博ZiBo
2025/01/06
1150
设计模式【8】-- 手工耿教我写装饰器模式
https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/设计模式.png
秦怀杂货店
2022/02/15
2380
设计模式【8】-- 手工耿教我写装饰器模式
设计模式日记(Decorator)-装饰器模式
装饰器(Decorator)模式指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。采用装饰模式扩展对象的功能比采用继承方式更加灵活;可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。但是装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。
六个核弹
2022/12/23
2590
【设计模式自习室】幕后英雄:装饰模式
该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。
Rude3Knife的公众号
2020/01/17
4880
【设计模式自习室】幕后英雄:装饰模式
推荐阅读
相关推荐
3年工作必备 装饰器模式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验