一、什么是装饰者模式
装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。
《Head First设计模式》书中有具体的介绍,还有例子说明,可以作为学习入门的参考。以下为对应的链接
https://blog.csdn.net/qq_33404395/article/details/80510909
装饰者模式在Java中的应用,最有名的莫过于I/O标准库的设计,网上也有大量的资料可以作为装饰者模式学习的参考。本文章主要还是以装饰者模式在mybatis框架中的应用做一下总结。
二、装饰者模式的应用
1、mybatis的缓存机制
首先,让我们来看下mybatis的缓存机制的接口,以及其接口实现。
接口Cache,去除注释后,可以清晰的看到,其缓存机制就是采用的以下几个接口方法去实现的。
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
对应的实现类分批进行截图
其实从实现类的命名,也可以大致猜到各个实现类的作用,或者起到的装饰作用是什么,那么装饰器模式必然有一个是具体实现功能的,其他的作为装饰器对其进行增强,而框架集成过程中又会通过配置的形式,在项目启动的时候,根据配置选择对应的装饰器进行装饰。那先来看下具体的缓存实现类PerpetualCache,相当简单粗暴的实现方法。缓存机制采用的其实就是HashMap进行实现的,由于代码比较简单,不过多进行介绍,因为这个不是重点。
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
通过实现类可以看出,框架一共提供了十个装饰器去装饰这一个具体的实现。
以mybatis缓存机制实现的类图来对该设计模式进行说明,这里以LoggingCache举例:
Cache定义了缓存机制所需要实现的接口,PerpetualCache作为缓存机制具体的实现类,其底层进行了缓存的具体实现。然而这个类也仅仅只是实现了最基础的功能,如果我们现在需要实现一个日志的实现,那么在PerpetualCache中进行添加,那是相当糟糕的做法,因为后面可能会有更多的需求,不断的添加会使得这个类显得很冗余且不灵活,而且没法很好的进行扩展,例如:我现在不想要日志功能,那么可能就要删除代码处理。这样的代码迭代将会很糟糕,而且这不符合软件设计的开闭原则,采用装饰者模式就很好的解决了这个问题。
查看LoggingCache的构造方法,通过构造方法传参传入delegate对象,而该对象既是Cache,通过在各个方法中进行装饰增强。
查看LoggingCache代码不难看出,LoggingCache只是对于getObject方法进行了命中日志的实现,而且采用的是debug日志。装饰者模式最终底层还是调用了被装饰者delegate,这个就是构造方法中传入的delegate对象,然后,在该对象调用具体getObject方法的同时,对其进行日志的装饰。
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
// 日志实现
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
通过配置的形式,最终通过CacheBuilder进行创建,该类明显的采用了建造者模式,build代码如下:
private final List<Class<? extends Cache>> decorators;
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;
}
newBaseCacheInstance方法实现了实现类的创建,通过类反射机制,通过字符串id传参创建,Cache的默认实现类中,只有PerpetualCache的创建方法是字符串传参,如果设置的实现类是PerpetualCache,则会将设置的decorators装饰者对Cache进行装饰。
CacheBuilder的build方法由MapperBuilderAssistant进行调用,查看建造代码:
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
由这一部分代码可以看出,默认创建的就是PerpetualCache类的实现,而默认会添加LruCache进行装饰,该类采用了lru算法的实现。
继续往上追踪,可以看出其配置提供了两种方式,注解形式,和xml形式,读取配置不详细涨开。
2、mybatis的缓存机制采用装饰者模式设计的优缺点
优点:
1、开闭原则,对扩展开放,对修改关闭。如果要实现一个新的功能,则实现对应的接口,并配置对应的装饰者,而这一切并不需要改动原有代码的实现。
2、单一职责原则,具体的缓存实现由PerpetualCache具体的类实现,其他功能,例如二级缓存,日志,序列化等等由具体的装饰者实现。各个类实现各个类所需要实现的具体功能。
3、合成复用原则,mybatis的缓存机制很好的可以通过配置,选取对应的组合来实现具体的缓存功能,而非采用继承的关系达到代码复用。假设采用继承的方式,那么各种组合,将会产生大量的类,而这样即不方便管理,且显得冗余。
缺点:
1、装饰者可能只需要对某个方法进行装饰,例如LoggingCache仅装饰getObject,可是却需要实现其他方法。
2、层层嵌套,比较难以排错,不过只要控制好对应的日志,还有报错,这个问题不大,例如缓存报错会抛出CacheException,而该异常的message可以作为后续定位问题的关键。