定义
装饰者模式也称为包装模式(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<DeferredLoad>(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; } //省略其他代码
而当我们初始化时,会对PerpetualCache 进行包装,查看CacheBuilder 我们就可以看出。
MyBatis 一级缓存结构图
如上结构图所示:
1.Cache 作为抽象组件定义了存取值的相关方法
2.PerpetualCache 作为具体被装饰者,实现了Cache里的相关方法
3.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); } } //最后附加上标准的装饰者 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来识别 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); } }
总结
本文简单的介绍了装饰者模式,装饰者模式也是一种比较常用的模式。主要运用在需要给客户装饰很多特性时,例如,给人穿衣服就一个很好的装饰模式应用场景。