装饰模式:动态的为对象增加新的功能,用于代替继承,避免类型体系的快速膨胀(熟悉代码规则的应该都知道,类往上追溯父类,层级不能超过5)。
装饰模式的组成:
1. Component:组件接口
2. ConcreteComponent:真实组件接口,对象可以被真实装饰组件动态添加新的功能
3. Decorator:抽象装饰组件
4. ConcreteDecorator:真实装饰组件,同时也实现组件接口,传入组件接口对象,增加新的功能
MyBatis的缓存功能源码包cache就是装饰模式的应用,下面是缓存包的包结构:
从上面的包结构图可知内容如下:
1. 接口Cache,等价于装饰模式中的组件接口:Component
2. Cache的默认实现类PerpetualCache,等价于装饰模式中的真实组件接口:ConcreteComponent
3. decorators包下的任一类,等价于装饰模式中的真实装饰组件:ConcreteDecorator
我们举简单的先进先出的缓存策略实现类:FifoCache为例,来说明装饰组件具体是怎么完成工作的。
首先,实现Cache接口,重写接口的方法,使具备Cache接口的功能。
public class FifoCache implements Cache {}
其次,通过构造器传参持有了被装饰对象:
private final Cache delegate;
// 传入的是实现或未实现缓存策略的Cache接口实现类
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList
然后,FifoCache重写了Cache的功能,除需要动态添加新功能的方法外,其余调用的都是传入的缓存对象的方法。
@Override
public String getId() {
return delegate.getId();
}
最终,也是最重要的,通过在调用传入的缓存对象的方法之前,调用私有成员方法添加新的功能:
@Override
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
decorators包下的其余的缓存策略实现类,都和FifoCache一样,实现了装饰模式。
适配器模式: 将一个类的接口转换成希望的另外一个接口。
适配器模式的组成:
1. 目标接口:Target
2. 适配器:Adapter
3. 需要适配的类:Adaptee
MyBatis的日志功能源码包logging就是适配器模式的应用,主要是为了将市场的各种日志插件,转换为MyBatis的日志接口,统一使用,下面是日志包的包结构:
从上面的包接口,我们看出,MyBatis为我们常见的日志框架:log4j、log4j2、slf4j等做了适配。
1. 接口Log就是要转化的目标接口:Target
2. 各个子包下的实现类,就是适配器:Adapter
3. 各个日志框架的日志类,比如org.apache.log4j.Logger就是需要适配的类:Adaptee
我们举实现了log4j日志接口Logger到MyBatis日志接口Log的转换类:Log4jImpl为例,说明适配器模式是怎么工作的。
首先,Log4jImpl实现了Log接口,实现Log接口的方法。
public class Log4jImpl implements Log {}
其次,持有Logger对象的引用,并且调用Log接口的方法,实际上是通过调用Logger对象的方法实现的。
private final Logger log;
public Log4jImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
通过上面的分析,我们可以看到装饰模式和适配器模式有很多的异同之处:
1. 两者都可以动态的添加新的功能,不过适配器模式着重的是转化为目标对象需要的接口,而不是添加新的功能。
2. 对于被包裹的对象而言,适配器模式是知道其实现详情的,而装饰器模式则不需要。