装饰者模式(开闭原则)-Decorator
定义:不改变原有对象的情况下,给一个对象扩展功能
应用:
JAVA I/O中的装饰者模式
抽象构件:Component(抽象类)-------------相当于I/O流里面InputStream/OutputStream和Reader/Writer
具体构件:ConcreteComponent(类)-------相当于I/O里面的FileOutputStream和FileInputStream。
装饰角色:Decorator(类)---------------------相当于I/O流里面的FilterOutputStream过滤流类
具体装饰:ConcreteDecorator(对象)------相当于I/O流里面的BufferedOutputStream缓冲流类等
流名称(省略inputStream) | 应用场景 | 装饰者中扮演的角色 |
---|---|---|
ByteArray | 访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上 | 具体构件 |
StringBuffer | 把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷 | 具体构件 |
File | 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作 | 具体构件 |
Piped | 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯 | 具体构件 |
Sequence | 把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来 | 具体构件 |
Data | 特殊流,读各种基本类型数据,如byte、int、String的功能 | 具体装饰 |
Object | 对象流,读对象的功能 | 具体构件 |
PushBack | 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中 | 具体装饰 |
Buffered | 缓冲流,增加了缓冲功能 | 具体装饰 |
LineNumber | 额外的功能是它增加了目标文件中的行号 | 具体装饰 |
全部I/O流(具体的i/o流章节细讲)
大致是:
1.我要对字节,字符流进行装饰。
2.我要对具体的文件形式的字节字符流进行装饰。
3.我要让过滤流类作为**装饰(抽象)**来去对具体指定对象进行装饰
(看出来程序员不管搞什么都很细节),像我这个菜鸡写代码就想不到直接装饰设成抽象,一定是具体装饰写了巨多种情况后,才会想到用抽象装饰去封装一波。
4.我要让缓冲流类等等作为具体装饰来对具体指定对象进行装饰
JAVA I/O实例代码:
BufferedReader xxx = new BufferedReader(new InputStreamReader(new DeflaterInputStream(new DataInputStream(new FileInputStream(new File("xxx"))))));
// 抽象的来说就是可以给想要装饰的角色一直套娃
而I/O流的抽象装饰之所以存在的意义还是因为多种具体装饰肯定会有共同的方法,所以在Reader和BufferedReader这种具体装饰之间才需要抽象装饰来实现相同方法来简化,然而BufferedReader字符流压根没有与其他类有相同的方法,或者说有相同的方法在Reader这个抽象类里了,所以压根不需要继承FilterReader,而BufferInputStream则不一样,可能有很多具体的装饰与其有相同方法,所以要经过一个FilterInputStream。
有意思的是:由上面几张图可以看到,它们都调用了它们父类的构造,来完成初始化,Filter是包私有的,所以就算它是普通类,你在idea里可以拿到,但是用不了,如果你不了解里面的构造原理,压根不知道为什么这个类我能拿到,却用不了,而且Reader和InputStream也不是接口,所以我觉着这里IO流用装扮者模式很牵强,扩展功能的时候为什么不用接口去搞啊,为什么非要实现一个Filter来专门实现一个装饰者,,为以后的功能进行扩展,我暂时的理解是因为虽然同样是扩展功能,但是功能与功能之间又不是相互独立的而是互相参杂的,导致功能扩展不好用接口,而用装扮者来拓展功能更合适一点,我暂时也想不到其他好的模式。
需要注意的是:是装饰者和被装饰者必须达到类型匹配,这样他们才能组合在一起,然后共同去描述和修饰某一事物。
还有一个值得注意的点:
BufferedReader初始化时,默认缓冲区大小是8192字符数组,而BufferedInputStream初始化时,默认缓冲区大小是8192字节数组,也就是一个16k,一个8k,需要分清楚。
JAVA mybatis中 二级缓存的装饰者模式
mybatis的装饰者直接放在了/ibatis/cache/decorators,(很明显)
查看它的UML图,刚开始说它是装饰者我是有点不信的,后来仔细看是把装饰者简化了
仔细看一下它的目录结构,其他的都作为装饰器放在了decorators,而PerpetualCache作为接口的实现工具类放在了impl包下。所以它作为具体构件即被装饰者,而其他都是装饰者。
mybatis对装饰者模式进行了简化但是核心仍在
也就是实现装饰者的核心,一定要保证装饰者和被装饰者的类型一致,所以装饰者初始化时一定传入的时大家所共有的接口或抽象类(超类),来保证我保存上一个被装饰者的功能和传给下一个装饰者时的功能的一个完整适配。
缓存类型 | 作用 | 角色 |
---|---|---|
FifoCache | 先进先出算法,缓存回收策略 | 装饰者 |
LoggingCache | 输出缓存命中的日志信息 | 装饰者 |
LruCache | 最近最少使用算法,缓存回收策略 | 装饰者 |
ScheduledCache | 调度缓存,负责定时清空缓存 | 装饰者 |
SerializedCache | 缓存序列化和反序列化存储 | 装饰者 |
SoftCache | 基于软引用实现的缓存管理策略 | 装饰者 |
SynchronizedCache | 同步的缓存装饰器,用于防止多线程并发访问 | 装饰者 |
WeakCache | 基于弱引用实现的缓存管理策略 | 装饰者 |
TransactionalCache | 事务缓存 , 一次性存入或移除多个缓存 | 装饰者 |
PerpetualCache(一级) | 永久缓存 ,一旦存入就一直保持 | 被装饰者 |
- 一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中
(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以
一级缓存的生命周期与SqlSession是相同的。 - 二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中
总结:
优点:
1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合
缺点:
1.这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。