结构型模式                 ————顺口溜:适装桥组享代外

目录

1、装饰器模式

1.1 装饰器模式UML图

1.2 日常生活中看装饰器模式

1.3 使用场景

1.4 Java代码实现

2、装饰器模式在源码中的应用

2.1 MyBatis源码中装饰器模式

2.1.1 Executor

2.1.2 CachingExecutor (装饰器的具体实现对象)

2.1.3 Cache

2.1.4 具体使用

2.2 Spring源码中装饰器模式

2.2.1 Decorator例子

2.2.2 Wrapper例子

3、装饰器模式的优缺点

3.1 优点

3.2 缺点

3.3 使用场景

3.4 注意事项

4、装饰器模式与代理模式异同


1、装饰器模式

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

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

  • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
  • 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
  • 何时使用:在不想增加很多子类的情况下扩展类。
  • 如何解决:将具体功能职责划分,同时继承装饰者模式。
  • 关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

1.1 装饰器模式UML图

1.2 日常生活中看装饰器模式

  1. 搭配不同服饰的系统(组装好似建造者模式,但建造者模式要求建造的过程必须是稳定的,而此处完全可以内裤外穿变超人,不属于建造者模式)——引自《大话设计模式》
  2. 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
  3. 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

1.3 使用场景

  1. 动态的增加对象的功能;
  2. 不能以派生子类的方式来扩展功能;
  3. 动态增加功能,动态撤销
  4. 限制对象的执行条件;
  5. 参数控制和检查等;

1.4 Java代码实现

写这篇博客的初衷也是恰好在工作中使用到了这个模式,觉得非常好用。需求大致是这样:采用sls服务监控项目日志,以Json的格式解析,所以需要将项目中的日志封装成json格式再打印。现有的日志体系采用了log4j + slf4j框架搭建而成。调用起来是这样的:

private static final Logger logger = LoggerFactory.getLogger(Component.class);
logger.error(string);

这样打印出来的是毫无规范的一行行字符串。在考虑将其转换成json格式时,我采用了装饰器模式。目前有的是统一接口Logger和其具体实现类,我要加的就是一个装饰类和真正封装成Json格式的装饰产品类。具体实现代码如下:

/*** logger decorator for other extension * this class have no specific implementation* just for a decorator definition* @author jzb**/
public class DecoratorLogger implements Logger {public Logger logger;    public DecoratorLogger(Logger logger) {        this.logger = logger;    }    @Overridepublic void error(String str) {}@Overridepublic void info(String str) {}//省略其他默认实现
}
/*** json logger for formatted output * @author jzb**/
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) {super(logger);}@Overridepublic void info(String msg) {JSONObject result = composeBasicJsonResult();result.put("MESSAGE", msg);logger.info(result.toString());}@Overridepublic void error(String msg) {JSONObject result = composeBasicJsonResult();result.put("MESSAGE", msg);logger.error(result.toString());}public void error(Exception e) {JSONObject result = composeBasicJsonResult();result.put("EXCEPTION", e.getClass().getName());String exceptionStackTrace = ExceptionUtils.getStackTrace(e);    result.put("STACKTRACE", exceptionStackTrace);logger.error(result.toString());}public static class JsonLoggerFactory {@SuppressWarnings("rawtypes")public static JsonLogger getLogger(Class clazz) {Logger logger = LoggerFactory.getLogger(clazz);return new JsonLogger(logger);}}private JSONObject composeBasicJsonResult() {//拼装了一些运行时信息}
}

可以看到,在JsonLogger中,对于Logger的各种接口,我都用JsonObject对象进行一层封装。在打印的时候,最终还是调用原生接口logger.error(string),只是这个string参数已经被我们装饰过了。如果有额外的需求,我们也可以再写一个函数去实现。比如error(Exception e),只传入一个异常对象,这样在调用时就非常方便了。

另外,为了在新老交替的过程中尽量不改变太多的代码和使用方式。我又在JsonLogger中加入了一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger中可能更好一些),他包含一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下:

    private static final Logger logger = JsonLoggerFactory.getLogger(Component.class);logger.error(string);

他唯一与原先不同的地方,就是LoggerFactory -> JsonLoggerFactory,这样的实现,也会被更快更方便的被其他开发者接受和习惯。

2、装饰器模式在源码中的应用

2.1 MyBatis源码中装饰器模式

MyBatis中关于Cache和CachingExecutor接口的实现类也使用了装饰者设计模式。Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是合适的。

2.1.1 Executor

首先我们看下Executor,打开MyBatis的源码org.apache.ibatis.session.Configuration

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}//如果开启了二级缓存则装饰原先的Executorif (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

2.1.2 CachingExecutor (装饰器的具体实现对象)

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

当然,这个装饰器模式的使用与标准的有点差异,但是完成的功能性质相同。

2.1.3 Cache

在MyBatis中有一级和二级缓存。在BaseExecutor(SimpleExecutor\BatchExecutor的父类)中,存放着一级缓存,org.apache.ibatis.cache.impl.PerpetualCache 是默认缓存的实现。

而当我们初始化时,会对PerpetualCache进行包装
查看org.apache.ibatis.mapping.CacheBuilder中源码,我们开启二级缓存后会包装多层,比如加上LruCache来进行缓存的清除等。

  public Cache build() {setDefaultImplementations();Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);// issue #352, do not apply decorators to custom caches//加上一些装饰if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}cache = setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache;}

MyBastis中的装饰器实现对象

包装完成之后呢,在查询等方法进行缓存操作的时候,就拥有了更强大的功能了。

2.1.4 具体使用

好了,说了这么多,我们看下它是如何使用装饰器模式的吧

Cache 组件对象的接口
PerpetualCache 具体组件,是我们需要装饰的对象
LruCache等 是具体装饰类,被装饰的对象

2.2 Spring源码中装饰器模式

Spring 中用到的装饰器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有Decorator。

2.2.1 Decorator例子

在 Spring 中,TransactionAwareCacheDecorator 类相当于装饰器模式中的抽象装饰角色,主要用来处理事务缓存,代码如下。

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;}......
}

TransactionAwareCacheDecorator 就是对 Cache 的一个包装。

下面再来看一个 MVC 中的装饰器模式:HttpHeadResponseDecorator 类,相当于装饰器模式中的具体装饰角色。

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator{public HttpHeadResponseDecorator(ServerHttpResponse delegate){super(delegate);}...
}

2.2.2 Wrapper例子

Spring Session就是其中之一,Spring Session通过SessionRepositoryRequestWrapper继承ServletRequestWrapper,扩展了Request,并在SessionRepositoryFilter通过调用过滤链filterChain.doFilter(strategyRequest, strategyResponse);将装饰的Request传入下一流程,具体请阅读以下类图的实现:

SessionRepositoryRequestWrapper覆盖了以下方法:

@Override
public boolean isRequestedSessionIdValid();@Override
public HttpSessionWrapper getSession(boolean create);@Override
public ServletContext getServletContext();@Override
public HttpSessionWrapper getSession();@Override
public String getRequestedSessionId();

3、装饰器模式的优缺点

3.1 优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合。是继承的一个替代模式,换句话说,Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
  • 装饰器模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component(因为Decorator本身就是继承自Component的),实现的还是is-a的关系。
  • 装饰模式可以动态地扩展一个实现类的功能,比如在I/O系统中,我们直接给BufferedInputStream的构造器直接传一个InputStream就可以轻松构件一个带缓冲的输入流,如果需要扩展,我们继续“装饰”即可。

3.2 缺点

多层的装饰是比较复杂的。

为什么会复杂?你想想看,就像剥洋葱一样,你剥到最后才发现是最里层的装饰出现了问题,可以想象一下工作量。这点从我使用Java I/O的类库就深有感受,我只需要单一结果的流,结果却往往需要创建多个对象,一层套一层,对于初学者来说容易让人迷惑。

3.3 使用场景

  1. 扩展一个类的功能。
  2. 动态增加功能,动态撤销。

3.4 注意事项

可代替继承。

4、装饰器模式与代理模式异同

  • 代理模式和装饰模式看代码差不多,实际上二者的着重点不同
  • 代理模式偏向于一个控制器,就是把被代理对象的控制权交给了代理对象,由代理对象决定被代理对象是否执行,如何执行
  • 装饰模式偏向于一个和花瓶,只能在被装饰对象前后加入一点东西,并不会去阻止执行的

其实所谓装饰器,本质上是对现有类对象的包裹,得到一个加强版的对象。

参考文章:

https://www.cnblogs.com/jzb-blog/p/6717349.html

https://www.runoob.com/design-pattern/decorator-pattern.html

https://blog.csdn.net/qq_18860653/article/details/80594778

http://c.biancheng.net/view/8464.html

https://www.cnblogs.com/nick-huang/p/7009511.html

装饰器模式在MyBatis以及Spring源码中的应用相关推荐

  1. 【设计模式系列20】解释器模式原理及其在JDK和Spring源码中的体现

    解释器模式原理及其在JDK源码中的体现 设计模式系列总览 前言 什么是解释器模式 终结符表达式和非终结符表达式 解释器模式示例 解释器模式角色 解释器模式在JDK和Spring源码中应用 解释器模式应 ...

  2. 工厂设计模式(java版本、spring源码中使用的工厂模式)

    最近在学习spring源码,发现在IoC容器初始化的时候创建各种bean,然后在代码中看到各种beanFactory和factoryBean,很显然spring容器在创建bean的过程是使用了工厂设计 ...

  3. Spring源码中getBean的简单流程

    在学习Sring的时候,免不了经常见如下的代码 VirtuousApplicationContext applicationContext = new VirtuousApplicationConte ...

  4. 设计模式--spring源码中使用策略模式(Strategy Pattern)

    转载http://liuxi1024.iteye.com/blog/583145 策略模式(Strategy Pattern)中体现了两个非常基本的面向对象设计的基本原则:封装变化的概念:编程中使用接 ...

  5. 代理模式在Spring 源码中的应用

    先看ProxyFactoryBean 核心的方法就是getObject()方法,我们来看一下源码: public Object getObject() throws BeansException {i ...

  6. 注释部分为spring循环依赖的解释,由于方便调阅源码使用了很对{@link},所以讲这文件放在spring源码中阅读会比较方便

    /* Copyright 2002-2019 the original author or authors. Licensed under the Apache License, Version 2. ...

  7. 从框架源码中学习结构型设计模式

    文章目录 从框架源码学习结构型设计模式 适配器模式 应用实例 案例一:dubbo框架日志适配器 Logger接口 日志实现类 Logger适配器接口 LoggerAdapter实现类 Logger日志 ...

  8. 3年工作必备 装饰器模式

    故事 古话说的好:人靠衣裳马靠鞍.下面先带大家来熟悉这句话的背景: 人靠衣装马靠鞍,狗配铃铛跑的欢出自沈自晋<望湖亭记>第十出:"虽然如此,佛靠金装,人靠衣装,打扮也是很要紧的. ...

  9. 设计模式之门面模式与装饰器模式详解和应用

    目录 1 门面模式定义 1.1 门面模式的应用场景 1.2 门面模式的通用写法 1.3 门面模式业务场景实例 1.4 门面模式在源码中的应用 1.5 门面模式的优缺点 2 装饰器模式 2.1 装饰器模 ...

最新文章

  1. Android移动开发之【通往安卓的神奇之旅】Android的五大布局和AndroidManifest
  2. /dev、/sys/dev 和/sys/devices 和udev的关系
  3. postgres 退出_centos如何退出postges?
  4. python获取当时文件目录_python获取文件目录
  5. idea .defaultMessage
  6. 开发函数计算的正确姿势 —— Fun validate 语法校验排错指南
  7. leetcode python 1
  8. Cause: java.sql.SQLException: Unknown initial character set index ‘255‘ received from server. Initia
  9. Linux监控工具Spotlight on Unix
  10. php5apache2.dll,Apache2.2.8 + PHP5.2.5不能加载php5apache2.dll
  11. C语言基础—进制转换
  12. 人力资源数据分析师前景_HR熬出头了!人力资源数据分析师年薪18万-90万
  13. linux搜索word文档内容,在目录中搜索MS word文件以获取Linux中的特定内容
  14. Linux kali 安装 qq Tim
  15. 利用SPSS做数据分析①(了解SPSS)
  16. Unity网络编程一: 基于Socket搭建一个服务器
  17. 安装TortoiseSVN报could not write value to key的错误解决方案
  18. 机械设计课程设计含设计说明书
  19. [转]现代密码学实践指南
  20. 下载安装linux RedHat

热门文章

  1. MATLAB图像二值化
  2. java面试笔记二:java的面向对象
  3. edger和deseq2_转录组分析(二)Hisat2+DESeq2/EdgeR
  4. org.codehaus.jettison.json.JSONObject类型的Null值怎么判断的问题,JSONObject$Null
  5. 备赛日记 CCPC2022 高职专场 ABDEHIJK 8/12
  6. 4K大军压竞!技嘉AORUS 4K战术型电竞萤幕全球开卖
  7. 查询壹米滴答大量物流信息,分析正在派件的单号
  8. JSqlParser入门教程之常用示例
  9. java破坏双亲委派_JDK为何自己先破坏双亲委派模型?
  10. 超8成项目存在高危开源漏洞 《2021中国软件供应链安全分析报告》发布