结构型模式 ————顺口溜:适装桥组享代外
目录
2.1.2 CachingExecutor (装饰器的具体实现对象)
1、装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
- 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
- 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
- 何时使用:在不想增加很多子类的情况下扩展类。
- 如何解决:将具体功能职责划分,同时继承装饰者模式。
- 关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
1.1 装饰器模式UML图
1.2 日常生活中看装饰器模式
- 搭配不同服饰的系统(组装好似建造者模式,但建造者模式要求建造的过程必须是稳定的,而此处完全可以内裤外穿变超人,不属于建造者模式)——引自《大话设计模式》
- 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
- 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
1.3 使用场景
- 动态的增加对象的功能;
- 不能以派生子类的方式来扩展功能;
- 动态增加功能,动态撤销
- 限制对象的执行条件;
- 参数控制和检查等;
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; }
@Override
public void error(String str) {}
@Override
public void info(String str) {}
//省略其他默认实现
}
/**
* json logger for formatted output
* @author jzb
*
*/
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) {
super(logger);
}
@Override
public void info(String msg) {
JSONObject result = composeBasicJsonResult();
result.put("MESSAGE", msg);
logger.info(result.toString());
}
@Override
public 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);
}
//如果开启了二级缓存则装饰原先的Executor
if (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);
}
@Override
public 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 使用场景
- 扩展一个类的功能。
- 动态增加功能,动态撤销。
3.4 注意事项
可代替继承。
4、装饰器模式与代理模式异同
- 代理模式和装饰模式看代码差不多,实际上二者的着重点不同
- 代理模式偏向于一个控制器,就是把被代理对象的控制权交给了代理对象,由代理对象决定被代理对象是否执行,如何执行
- 装饰模式偏向于一个和花瓶,只能在被装饰对象前后加入一点东西,并不会去阻止执行的
其实所谓装饰器,本质上是对现有类对象的包裹,得到一个加强版的对象。
参考文章:
https://www.cnblogs.com/jzb-blog/p/6717349.html
https://www.runoob.com/design-pattern/decorator-pattern.html
https://blog.youkuaiyun.com/qq_18860653/article/details/80594778