天天看点

python 多装饰器在类上_灵活又强大的装饰器模式实例与应用源码解析

装饰器模式,又称为包装模式,是一种结构型模式。这种设计模式是指能够在一个类的基础上增加一个装饰类(也可以叫包装类),并在装饰类中增加一些新的特性和功能。这样,通过对原有类的包装,就可以在不改变原有类的情况下为原有类增加更多的功能。

例如我们定义Phone接口,它规定了发送和接收语音的抽象方法。

public interface Phone {    String callIn();    Boolean callOut(String info);}
           

然后定义一个类TelePhone,实现了Phone接口,能够实现打电话的功能。

public class TelePhone implements Phone {    @Override    public String callIn() {        System.out.println("接受语音……");        return "get info";    }    @Override    public Boolean callOut(String info) {        System.out.println("发送语音:" + info);        return true;    }}
           

那现在我们要创建一个装饰器,在不改变原有TelePhone的基础上,实现通话录音功能。我们的装饰器类的源码如下所示。

public class PhoneRecordDecorator implements Phone {    private Phone decoratedPhone;    public PhoneRecordDecorator(Phone decoratedPhone) {        this.decoratedPhone = decoratedPhone;    }    @Override    public String callIn() {        System.out.println("启动录音……");        String info = decoratedPhone.callIn();        System.out.println("结束录音并保存录音文件。");        return info;    }    @Override    public Boolean callOut(String info) {        System.out.println("启动录音……");        Boolean result = decoratedPhone.callOut(info);        System.out.println("结束录音并保存录音文件。");        return result;    }}
           

这样,经过我们的PhoneRecordDecorator包装过的Phone就具有了通话录音的能力,如下所示。

System.out.println("--原有Phone无录音功能--");Phone phone = new TelePhone();phone.callOut("Hello, this is yee.");System.out.println();System.out.println("--经过装饰后的Phone有录音功能--");Phone phoneWithRecorder = new PhoneRecordDecorator(phone);phoneWithRecorder.callOut("Hello, this is yee.");
           

运行结果如图所示。

python 多装饰器在类上_灵活又强大的装饰器模式实例与应用源码解析

本示例中,我们使用装饰器模式对被包装类的功能进行了扩展,但是不影响原有类。遵照这个思想,还可以通过包装类增加新的方法、属性等。例如,我们给原来的TelePhone类增加收发短信功能,如下所示。

public class PhoneMessageDecorator implements Phone {    private Phone decoratedPhone;    public PhoneMessageDecorator(Phone decoratedPhone) {        this.decoratedPhone = decoratedPhone;    }    @Override    public String callIn() {        return decoratedPhone.callIn();    }    @Override    public Boolean callOut(String info) {        return decoratedPhone.callOut(info);    }    public String receiveMessage() {        // 省略接受短信操作        return "receive message";    }        public Boolean sendMessage(String info) {        // 省略发送短信操作        return true;    }}
           

装饰器模式在编程开发中经常使用。通常的使用场景是在一个核心基本类的基础上,提供大量的装饰器,从而使得核心基本类经过不同的装饰器修饰后获得不同的功能。(均参考自《通用源码阅读指导书——MyBatis源码详解》)

装饰器还有一个优点就是可以叠加使用,即一个核心基本类可以被多个装饰器修饰,从而同时具有这多个装饰器的功能。

MyBatis便使用了大量的装饰器实现了不同类型的缓存,它们都在cache包中。在imple子包中存放了实现类,在decorators子包中存放了众多装饰器类。而Cache接口是实现类和装饰器类的共同接口。

下面给出了Cache接口及其子类的类图。Cache接口的子类中,只有一个实现类,但却有十个装饰器类。通过使用不同的装饰器装饰实现类可以让实现类有着不同的功能。

python 多装饰器在类上_灵活又强大的装饰器模式实例与应用源码解析

Cache接口的源码如下所示,在接口中定义了实现类和装饰器类中必须实现的方法。

public interface Cache {  /**   * 获取缓存id   * @return 缓存id   */  String getId();  /**   * 向缓存写入一条数据   * @param key 数据的键   * @param value 数据的值   */  void putObject(Object key, Object value);  /**   * 从缓存中读取一条数据   * @param key 数据的键   * @return 数据的值   */  Object getObject(Object key);  /**   * 从缓存中删除一条数据   * @param key 数据的键   * @return 原来的数据值   */  Object removeObject(Object key);  /**   * 清空缓存   */  void clear();  /**   * 读取缓存中数据的数目   * @return 数据的数目   */  int getSize();  /**   * 获取读写锁,该方法已经废弃   * @return 读写锁   */  default ReadWriteLock getReadWriteLock() {    return null;  }}
           

缓存实现类PerpetualCache的实现非常简单,但可以通过装饰器来为其增加更多的功能。decorators子包中存在许多装饰器,根据装饰器的功能可以将它们可以分为以下几个大类:

  • 同步装饰器:为缓存增加同步功能,如SynchronizedCache类。
  • 日志装饰器:为缓存增加日志功能,如LoggingCache类。
  • 清理策略装饰器:为缓存中的数据增加清理功能,如FifoCache类、LruCache类、WeakCache类、SoftCache类。
  • 阻塞装饰器:为缓存增加阻塞功能,如BlockingCache类。
  • 刷新装饰器:为缓存增加定时刷新功能,如ScheduledCache类。
  • 序列化装饰器:为缓存增加序列化功能,如SerializedCache类。
  • 事务装饰器:用于支持事务操作的装饰器,如TransactionalCache类。

《通用源码阅读指导书——MyBatis源码详解》一书中对于上述装饰器的实现进行了详细的介绍,我们不再一一展开。仅拿出做简要介绍。

FifoCache类是一个装饰器,经过它装饰的缓存会采用先进先出的策略来清理缓存,它内部使用了keyList属性存储了缓存数据的写入顺序,并且使用size属性存储了缓存数据的数量限制。当缓存中的数据达到限制时,FifoCache装饰器会将最先放入缓存中的数据删除。下面展示了FifoCache类的属性。

// 被装饰对象private final Cache delegate;// 按照写入顺序保存了缓存数据的键private final Deque keyList;// 缓存空间的大小private int size;
           

当向缓存中存入数据时,FifoCache类会判断数据数量是否已经超过限制。如果超过,则会将最先写入缓存的数据删除,下面展示了相关操作的源码。

/** * 向缓存写入一条数据 * @param key 数据的键 * @param value 数据的值 */@Overridepublic void putObject(Object key, Object value) {  cycleKeyList(key);  delegate.putObject(key, value);}/** * 记录当前放入的数据的键,同时根据空间设置清除超出的数据 * @param key 当前放入的数据的键 */private void cycleKeyList(Object key) {  keyList.addLast(key);  if (keyList.size() > size) {    Object oldestKey = keyList.removeFirst();    delegate.removeObject(oldestKey);  }}
           

其中的delegate引用的就是缓存最终的实现类,当然也可能是被装饰器装饰后的实现类。毕竟,我们也说过,装饰器可以叠加使用。

关于其他各种缓存装饰器的实现、装饰器的叠加使用等等,大家可以参考《通用源码阅读指导书——MyBatis源码详解》。这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍,总结了大量的编程知识和架构经验,对提升编程和架构能力十分有用,非常推荐。

python 多装饰器在类上_灵活又强大的装饰器模式实例与应用源码解析

书中对于Mybatis的各种缓存装饰器的实现源码都进行了介绍,看完就觉着,用好装饰器真是——灵活又强大!

最后,我是高级软件架构师易哥。

欢迎关注我,我会偶尔分享软件架构和编程方面的知识。