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

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

定义

装饰者模式也称为包装模式(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 一级缓存结构图

8b9b4aa233d762a926a1ac087d67a179_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

如上结构图所示:

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

总结

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


相关文章
|
4月前
|
设计模式 存储 Java
认真学习设计模式之观察者模式(Observer Pattern)
认真学习设计模式之观察者模式(Observer Pattern)
29 0
|
4月前
|
设计模式 Java
Java设计模式【十】:装饰者模式
Java设计模式【十】:装饰者模式
22 0
|
3月前
|
设计模式 监控 安全
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
62 0
|
2天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
4月前
|
设计模式 存储 Java
认真学习设计模式之命令模式(Command Pattern)
认真学习设计模式之命令模式(Command Pattern)
80 0
|
2月前
|
设计模式 缓存 安全
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
设计模式-代理模式(静态代理、动态代理、cglib代理)、代理模式和装饰者模式的区别
55 1
|
3月前
|
设计模式 安全 Java
多线程设计模式【线程安全、 Future 设计模式、Master-Worker 设计模式 】(一)-全面详解(学习总结---从入门到深化)
多线程设计模式【线程安全、 Future 设计模式、Master-Worker 设计模式 】(一)-全面详解(学习总结---从入门到深化)
28 0
|
3月前
|
设计模式 Go 开发工具
Golang设计模式——22装饰者模式
Golang设计模式——22装饰者模式
22 0
|
4月前
|
设计模式 Java 关系型数据库
认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式
认真学习设计模式之适配器模式(Adapter Pattern)/包装器模式
62 0
|
4月前
|
设计模式 Java
根据真实业务场景去实现一下设计模式中的装饰者模式
根据真实业务场景去实现一下设计模式中的装饰者模式
18 0