设计模式学习11----装饰者模式

定义

装饰者模式也称为包装模式(Wrapper Pattern),属于结构型设计模式。
在不改变原类文件以及不使用继承的情况下,动态地将责任附加到对象中,从而实现动态扩展一个对象的功能。它通过创建一个包装对象,也就是装饰来包裹真实对象。
结构类图
在这里插入图片描述

角色

抽象组件(Component): 定义装饰方法的规范
被装饰者(ConcreteComponent): Component的具体实现,也就是我们要装饰的具体对象
装饰者组件(Decorator): 持有组件(Component)对象的实例引用,该类的职责就是为了装饰具体组件对象,定义的规范。
具体装饰(ConcreteDecorator): 负责给构件对象装饰附加的功能

装饰者模式的优缺点
优点

把类汇总的装饰功能从类中搬出,扩展性十分良好
把类中的核心职责和装饰功能区分开来,结构清晰明了,并且可以去除相关类的重复装饰逻辑,灵活性好

缺点

会出现很多小类,即装饰类。

MyBatis中应用装饰模式

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

public abstract class BaseExecutor implements Executor {

private static final Log log = LogFactory.getLog(BaseExecutor.class);

protected Transaction transaction;
protected Executor wrapper;

//本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询(一级缓存)
//本地缓存
protected PerpetualCache localCache;
//本地输出参数缓存
protected PerpetualCache localOutputParameterCache;

protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue();
this.localCache = new PerpetualCache(“LocalCache”);
this.localOutputParameterCache = new PerpetualCache(“LocalOutputParameterCache”);
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
//省略其他代码

而当我们初始化时,会对PerpetualCache 进行包装,查看CacheBuilder 我们就可以看出。
MyBatis 一级缓存结构图

如上结构图所示:

Cache 作为抽象组件定义了存取值的相关方法
PerpetualCache 作为具体被装饰者,实现了Cache里的相关方法
LruCache 等作为具体的装饰者,持有了Cache对象的实例引用。

代码解析

CacheBuilder 类的build方法。CacheBuilder 作为客户端调用类。

public Cache build() {
// 1. 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
setDefaultImplementations();
//通过反射创建缓存
Cache cache = newBaseCacheInstance(implementation, id);
//设额外属性,初始化Cache对象
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
// 2. 仅对内置缓存PerpetualCache应用装饰器
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
//装饰者模式一个个包装cache
cache = newCacheDecoratorInstance(decorator, cache);
//又要来一遍设额外属性
setCacheProperties(cache);
}
//3. 应用标准的装饰者,比如LoggingCache,SynchronizedCache
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
//4.如果是custom缓存,且不是日志,要加日志
cache = new LoggingCache(cache);
}
return cache;
}

private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {
Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);
try {
  return cacheConstructor.newInstance(base);
} catch (Exception e) {
  throw new CacheException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);
}

}

//最后附加上标准的装饰者
  • 1

private Cache setStandardDecorators(Cache cache) {
try {
// 创建"元信息"对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter(“size”)) {
metaCache.setValue(“size”, size);
}
if (clearInterval != null) {
//刷新缓存间隔,怎么刷新呢,用ScheduledCache来刷,还是装饰者模式,漂亮!
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
//如果readOnly=false,可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
cache = new SerializedCache(cache);
}
//日志缓存
cache = new LoggingCache(cache);
//同步缓存, 3.2.6以后这个类已经没用了,考虑到Hazelcast, EhCache已经有锁机制了,所以这个锁就画蛇添足了。
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}

接着我们来看看具体被装饰者(PerpetualCache)类

/**

  • 永久缓存
  • 一旦存入就一直保持

*/
public class PerpetualCache implements Cache {

//每个永久缓存有一个ID来识别
  • 1

private String id;

//内部就是一个HashMap,所有方法基本就是直接调用HashMap的方法,不支持多线程?
private Map<Object, Object> cache = new HashMap<Object, Object>();

public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}

@Override
public Object getObject(Object key) {
return cache.get(key);
}
}

PerpetualCache 类很简单,我们就不做详细分析了。接着我们来看看具体装饰者LruCache类,该装饰器类主要作用是移除最近最少使用的缓存。

/*

  • 最近最少使用缓存
  • 基于 LinkedHashMap 覆盖其 removeEldestEntry 方法实现。
    */
    public class LruCache implements Cache {

private final Cache delegate;
//额外用了一个map才做lru,但是委托的Cache里面其实也是一个map,这样等于用2倍的内存实现lru功能
private Map<Object, Object> keyMap;
private Object eldestKey;

public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}

@Override
public String getId() {
return delegate.getId();
}

@Override
public int getSize() {
return delegate.getSize();
}

public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;

  //核心就是覆盖 LinkedHashMap.removeEldestEntry方法,
  //返回true或false告诉 LinkedHashMap要不要删除此最老键值
  //LinkedHashMap内部其实就是每次访问或者插入一个元素都会把元素放到链表末尾,
  //这样不经常访问的键值肯定就在链表开头啦
  @Override
  protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
    boolean tooBig = size() > size;
    if (tooBig) {
        //这里没辙了,把eldestKey存入实例变量
      eldestKey = eldest.getKey();
    }
    return tooBig;
  }
};

}

@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
//增加新纪录后,判断是否要将最老元素移除
cycleKeyList(key);
}

@Override
public Object getObject(Object key) {
//get的时候调用一下LinkedHashMap.get,让经常访问的值移动到链表末尾
keyMap.get(key); //touch
return delegate.getObject(key);
}
}

总结

本文简单的介绍了装饰者模式,装饰者模式也是一种比较常用的模式。主要运用在需要给客户装饰很多特性时,例如,给人穿衣服就一个很好的装饰模式应用场景。




作者:码农飞哥
微信公众号:码农飞哥