最近在學(xué)習(xí)MyBatis框架原理的時(shí)候,發(fā)現(xiàn)其實(shí)現(xiàn)二級(jí)緩存的過(guò)程中運(yùn)用到了裝飾者模式,所以來(lái)深入了解一下
介紹
裝飾者模式又名包裝(Wrapper)模式,以對(duì)客戶(hù)端透明的方式擴(kuò)展對(duì)象的功能,是繼承關(guān)系的一個(gè)替代方案
裝飾者模式動(dòng)態(tài)地將責(zé)任附加到對(duì)象身上,若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案
結(jié)構(gòu)
裝飾者模式以對(duì)客戶(hù)透明的方式動(dòng)態(tài)地給一個(gè)對(duì)象附加上更多的責(zé)任,客戶(hù)端并不會(huì)覺(jué)得對(duì)象在裝飾前和裝飾后有什么不同,裝飾者模式可以在不使用創(chuàng)造更多子類(lèi)的情況下,將對(duì)象的功能加以擴(kuò)展
裝飾者模式中的角色:
- 抽象構(gòu)件(Component):給出一個(gè)抽象接口,以規(guī)范準(zhǔn)備接收附加責(zé)任的對(duì)象
- 具體構(gòu)件(ConcreteComponent):定義一個(gè)將要接收附加責(zé)任的類(lèi)
- 裝飾者(Decorator):持有一個(gè)構(gòu)件(Component)對(duì)象的實(shí)例,并定義一個(gè)與抽象構(gòu)件接口一致的接口
- 具體裝飾者(ConcreteDecorator):負(fù)責(zé)給構(gòu)件對(duì)象加上附加的責(zé)任
抽象構(gòu)件
public interface Component{
public void sampleOperation();
}
具體構(gòu)件
public class ConcreteComponent implements Component{
@Override
public void sampleOperation(){
//相關(guān)業(yè)務(wù)代碼
}
}
裝飾者
public class Decorator implements Component{
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void sampleOperation() {
// 委派給構(gòu)件
component.sampleOperation();
}
}
具體裝飾者
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void sampleOperation() {
//相關(guān)的業(yè)務(wù)代碼
super.sampleOperation();
//相關(guān)的業(yè)務(wù)代碼
}
}
具體實(shí)現(xiàn)
定義被裝飾者
public interface Human {
public void wearClothes();
public void walkToWhere();
}
定義裝飾者
public abstract class Decorator implements Human {
private Human human;
public Decorator(Human human) {
this.human = human;
}
public void wearClothes() {
human.wearClothes();
}
public void walkToWhere() {
human.walkToWhere();
}
}
定義三種裝飾,這是第一個(gè),第二個(gè)第三個(gè)功能依次細(xì)化,即裝飾者的功能越來(lái)越多
public class Decorator_zero extends Decorator {
public Decorator_zero(Human human) {
super(human);
}
public void goHome() {
System.out.println("進(jìn)房子。。");
}
public void findMap() {
System.out.println("書(shū)房找找Map。。");
}
@Override
public void wearClothes() {
// TODO Auto-generated method stub
super.wearClothes();
goHome();
}
@Override
public void walkToWhere() {
// TODO Auto-generated method stub
super.walkToWhere();
findMap();
}
}
public class Decorator_first extends Decorator {
public Decorator_first(Human human) {
super(human);
}
public void goClothespress() {
System.out.println("去衣柜找找看。。");
}
public void findPlaceOnMap() {
System.out.println("在Map上找找。。");
}
@Override
public void wearClothes() {
// TODO Auto-generated method stub
super.wearClothes();
goClothespress();
}
@Override
public void walkToWhere() {
// TODO Auto-generated method stub
super.walkToWhere();
findPlaceOnMap();
}
}
public class Decorator_two extends Decorator {
public Decorator_two(Human human) {
super(human);
}
public void findClothes() {
System.out.println("找到一件D&G。。");
}
public void findTheTarget() {
System.out.println("在Map上找到神秘花園和城堡。。");
}
@Override
public void wearClothes() {
// TODO Auto-generated method stub
super.wearClothes();
findClothes();
}
@Override
public void walkToWhere() {
// TODO Auto-generated method stub
super.walkToWhere();
findTheTarget();
}
}
定義被裝飾者,被裝飾者初始狀態(tài)有些自己的裝飾
public class Person implements Human {
@Override
public void wearClothes() {
// TODO Auto-generated method stub
System.out.println("穿什么呢。。");
}
@Override
public void walkToWhere() {
// TODO Auto-generated method stub
System.out.println("去哪里呢。。");
}
}
測(cè)試類(lèi)
public class Test {
public static void main(String[] args) {
Human person = new Person();
Decorator decorator = new Decorator_two(new Decorator_first(
new Decorator_zero(person)));
decorator.wearClothes();
decorator.walkToWhere();
}
}
運(yùn)行結(jié)果
穿什么呢。。
進(jìn)房子。。
去衣柜找找看。。
找到一件D&G。。
去哪里呢。。
書(shū)房找找Map。。
在Map上找找。。
在Map上找到神秘花園和城堡。。
其實(shí)就是進(jìn)房子找衣服,然后找地圖這樣一個(gè)過(guò)程,通過(guò)裝飾者的三層裝飾,把細(xì)節(jié)變得豐富,關(guān)鍵點(diǎn):
- Decorator抽象類(lèi)中,持有Human接口,方法全部委托給該接口調(diào)用,目的是交給該接口的實(shí)現(xiàn)類(lèi)即子類(lèi)進(jìn)行調(diào)用
- Decorator抽象類(lèi)的子類(lèi)(具體裝飾者),里面都有一個(gè)構(gòu)造方法調(diào)用super(human),參數(shù)是Human接口,只要是該Human的實(shí)現(xiàn)類(lèi)都可以傳遞進(jìn)去,即表現(xiàn)出Decorator dt = new Decorator_second(new Decorator_first(new Decorator_zero(human)))這種結(jié)構(gòu)的樣子。所以當(dāng)調(diào)用dt.wearClothes()和dt.walkToWhere()的時(shí)候,又因?yàn)槊總€(gè)具體裝飾者類(lèi)中,都先調(diào)用super.wearClothes()和super.walkToWhere()方法,而該super已經(jīng)由構(gòu)造傳遞并指向了具體的某一個(gè)裝飾者類(lèi)(這個(gè)可以根據(jù)需要調(diào)換順序),那么調(diào)用的即為裝飾類(lèi)的方法,然后才調(diào)用自身的裝飾方法,既表現(xiàn)出一種裝飾、鏈?zhǔn)降念?lèi)似于過(guò)濾的行為
- 具體被裝飾者類(lèi),可以定義初始的狀態(tài)或者初始的自己的裝飾,后面的裝飾行為都在此基礎(chǔ)上一步一步進(jìn)行點(diǎn)綴、裝飾
- 裝飾者模式的設(shè)計(jì)原則:對(duì)擴(kuò)展開(kāi)放、對(duì)修改關(guān)閉,這句話(huà)體現(xiàn)在如果想擴(kuò)展被裝飾者類(lèi)的行為,無(wú)需修改裝飾者抽象類(lèi),只需繼承裝飾者抽象類(lèi),實(shí)現(xiàn)額外的一些裝飾或者叫行為即可對(duì)被裝飾者進(jìn)行包裝
- 觀察測(cè)試類(lèi)中調(diào)用的方式,與java的I/O操作十分相似
簡(jiǎn)化
大多數(shù)情況下,裝飾者模式的實(shí)現(xiàn)都比上面給出的例子要簡(jiǎn)單
如果只有一個(gè)ConcreteComponent類(lèi),那么可以考慮去掉抽象的Component類(lèi)(接口),把Decorator作為ConcreteComponent的子類(lèi)
如果只有一個(gè)ConcreteDecorator類(lèi),那么就沒(méi)有必要建立一個(gè)單獨(dú)的Decorator類(lèi),可以把Decorator和ConcreteDecorator的責(zé)任合并成一個(gè)類(lèi),甚至在只有兩個(gè)ConcreteDecorator的情況下,也可以這樣做
透明性要求
裝飾者模式對(duì)客戶(hù)端的透明性要求程序不要聲明一個(gè)ConcreteComponent類(lèi)型的變量,而應(yīng)當(dāng)聲明一個(gè)Component類(lèi)型的變量。
然而純碎的裝飾者模式很難找到,裝飾者模式的用意是在不改變接口的前提下,增強(qiáng)所考慮的類(lèi)的性能,在增強(qiáng)性能的時(shí)候,往往需要建立新的公開(kāi)的方法。這就導(dǎo)致了大多數(shù)的裝飾者模式的實(shí)現(xiàn)都是“半透明”的,而不是完全透明的,換言之,允許裝飾者模式改變接口,增加新的方法,這意味著客戶(hù)端可以聲明ConcreteDecorator類(lèi)型的變量,從而可以調(diào)用ConcreteDecorator類(lèi)中才有的方法。
半透明的裝飾者模式是介于裝飾者模式和適配器模式之間的,適配器模式的用意是改變所考慮的類(lèi)的接口,也可以通過(guò)改寫(xiě)一個(gè)或幾個(gè)方法,或增加新的方法來(lái)增強(qiáng)或改變所考慮的類(lèi)的功能。大多數(shù)裝飾者模式實(shí)際上是半透明的裝飾者模式,這樣的裝飾者模式也稱(chēng)作半裝飾、半適配器模式
裝飾者模式和適配器模式都是包裝模式(Wrapper Pattern),它們都是通過(guò)封裝其他對(duì)象達(dá)到設(shè)計(jì)的目的,但是它們的形態(tài)有很大區(qū)別
理想的裝飾者模式在對(duì)被裝飾對(duì)象進(jìn)行功能增強(qiáng)的同時(shí),要求具體構(gòu)件、裝飾者的接口與抽象構(gòu)件的接口完全一致,而適配器模式則不然,一般而言,適配器模式并不要求對(duì)源對(duì)象的功能進(jìn)行增強(qiáng),但是會(huì)改變?cè)磳?duì)象的接口,以便和目標(biāo)接口相符合
裝飾者模式有透明和半透明兩種,這兩種的區(qū)別就在于裝飾者的接口與抽象構(gòu)件的接口是否完全一致。透明的裝飾者模式也就是理想的裝飾者模式,要求具體構(gòu)件、裝飾者的接口與抽象構(gòu)件的接口完全一致。相反,如果裝飾者的接口與抽象構(gòu)件的接口不一致,也就是說(shuō)裝飾者的接口比抽象構(gòu)件的接口寬的話(huà),裝飾者實(shí)際上已經(jīng)成了一個(gè)適配器,這種裝飾者模式也是可以接受的,稱(chēng)為“半透明”的裝飾模式
在適配器模式里面,適配器類(lèi)的接口通常會(huì)與目標(biāo)類(lèi)的接口重疊,但往往并不完全相同,換言之,適配器類(lèi)的接口會(huì)比被裝飾的目標(biāo)類(lèi)接口寬
顯然,半透明的裝飾者模式實(shí)際上就是處于適配器模式與裝飾者模式之間的灰色地帶。如果將裝飾者模式與適配器模式合并成為一個(gè)“包裝模式”的話(huà),那么半透明的裝飾者模式倒可以成為這種合并后的“包裝模式”的代表
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 裝飾者模式與繼承關(guān)系的目的都是要擴(kuò)展對(duì)象的功能,但是裝飾者模式可以提供比繼承更多的靈活性。裝飾者模式允許系統(tǒng)動(dòng)態(tài)決定加上一個(gè)需要的裝飾,或者除掉一個(gè)不需要的裝飾。繼承關(guān)系則不同,繼承關(guān)系是靜態(tài)的,它在系統(tǒng)運(yùn)行前就決定了
- 通過(guò)使用不同的具體裝飾類(lèi)以及這些裝飾類(lèi)的排列組合,設(shè)計(jì)師可以創(chuàng)造出很多不同行為的組合
缺點(diǎn)
由于使用裝飾者模式,可以比使用繼承關(guān)系需要較少數(shù)目的類(lèi)。使用較少的類(lèi),當(dāng)然使設(shè)計(jì)比較易于進(jìn)行。但是,另一方面,使用裝飾者模式會(huì)產(chǎn)生比使用繼承關(guān)系更多的對(duì)象。更多的對(duì)象會(huì)使得查錯(cuò)變得困難,特別是這些對(duì)象看上去都很相像
Java IO流中的應(yīng)用
裝飾者模式在Java語(yǔ)言中最著名的應(yīng)用莫過(guò)于Java I/O標(biāo)準(zhǔn)庫(kù)的設(shè)計(jì)了
由于Java I/O庫(kù)需要很多性能的各種組合,如果這些性能都是用繼承的方法實(shí)現(xiàn)的,那么每一種組合都需要一個(gè)類(lèi),這樣就會(huì)造成大量性能重復(fù)的類(lèi)出現(xiàn)。而如果采用裝飾者模式,那么類(lèi)的數(shù)目就會(huì)大大減少,性能的重復(fù)也可以減至最少。因此裝飾者模式是Java I/O庫(kù)的基本模式
根據(jù)上圖可以看出:
- 抽象構(gòu)件(Component):由InputStream扮演,這是一個(gè)抽象類(lèi),為各種子類(lèi)型提供統(tǒng)一的接口
- 具體構(gòu)件(ConcreteComponent):由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類(lèi)扮演,它們實(shí)現(xiàn)了抽象構(gòu)建所規(guī)定的接口
- 抽象裝飾者:由FilterInputStream扮演,它實(shí)現(xiàn)了InputStream所規(guī)定的接口
- 具體裝飾者:由BufferedInputStream、DataInputStream以及兩個(gè)不常用到的類(lèi)LineNumberInputStream、PushbackInputStream扮演
InputStream類(lèi)型中的裝飾者模式是半透明的,看一下作為裝飾者模式中抽象構(gòu)件的InputStream的源代碼
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
下面是作為裝飾者模式中抽象裝飾者的FilterInputStream的源代碼
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte b[]) throws IOException {}
public int read(byte b[], int off, int len) throws IOException {}
public long skip(long n) throws IOException {}
public int available() throws IOException {}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public boolean markSupported() {}
}
FilterInputStream的接口與InputStream的接口是完全一致的
下面是具體裝飾者PushbackInputStream的源代碼
public class PushbackInputStream extends FilterInputStream {
private void ensureOpen() throws IOException {}
public PushbackInputStream(InputStream in, int size) {}
public PushbackInputStream(InputStream in) {}
public int read() throws IOException {}
public int read(byte[] b, int off, int len) throws IOException {}
public void unread(int b) throws IOException {}
public void unread(byte[] b, int off, int len) throws IOException {}
public void unread(byte[] b) throws IOException {}
public int available() throws IOException {}
public long skip(long n) throws IOException {}
public boolean markSupported() {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {}
public synchronized void close() throws IOException {}
}
這個(gè)裝飾類(lèi)提供了額外的方法unread(),這就意味著PushbackInputStream是一個(gè)半透明的裝飾類(lèi),它破壞了理想的裝飾者模式的要求。如果客戶(hù)端持有一個(gè)類(lèi)型為InputStream對(duì)象的引用in的話(huà),如果in的真實(shí)類(lèi)型是PushbackInputStream,只要客戶(hù)端不需要使用unread()方法,那么客戶(hù)端一般沒(méi)有問(wèn)題。但是如果客戶(hù)端必須使用這個(gè)方法,就必須進(jìn)行向下類(lèi)型轉(zhuǎn)換,將in的類(lèi)型轉(zhuǎn)換為PushbackInputStream之后才能調(diào)用這個(gè)方法。但是這個(gè)類(lèi)型轉(zhuǎn)換意味著客戶(hù)端必須知道它拿到的引用是指向一個(gè)類(lèi)型為PushbackInputStream的對(duì)象,這就破壞了使用裝飾者模式的原始用意
下面是使用I/O流讀取文件內(nèi)容的簡(jiǎn)單操作示例
public class IOTest {
public static void main(String[] args) throws IOException {
// 流式讀取文件
DataInputStream dis = null;
try{
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")
)
);
//讀取文件內(nèi)容
byte[] bs = new byte[dis.available()];
dis.read(bs);
String content = new String(bs);
System.out.println(content);
}finally{
dis.close();
}
}
}
最里層是一個(gè)FileInputStream對(duì)象,然后把它傳遞給一個(gè)BufferedInputStream對(duì)象,經(jīng)過(guò)BufferedInputStream處理,再把處理后的對(duì)象傳遞給了DataInputStream對(duì)象進(jìn)行處理,這個(gè)過(guò)程其實(shí)就是裝飾器的組裝過(guò)程,F(xiàn)ileInputStream對(duì)象相當(dāng)于原始的被裝飾的對(duì)象,而B(niǎo)ufferedInputStream對(duì)象和DataInputStream對(duì)象則相當(dāng)于裝飾器
MyBatis中的裝飾者模式
MyBatis在二級(jí)緩存實(shí)現(xiàn)中運(yùn)用到了裝飾者模式
當(dāng)開(kāi)一個(gè)會(huì)話(huà)時(shí),一個(gè)SqlSession對(duì)象會(huì)使用一個(gè)Executor對(duì)象來(lái)完成會(huì)話(huà)操作,MyBatis的二級(jí)緩存機(jī)制的關(guān)鍵就是對(duì)這個(gè)Executor對(duì)象做文章。如果用戶(hù)在mybatis-config.xml里配置了"cacheEnabled=true",那么MyBatis在為SqlSession對(duì)象創(chuàng)建Executor對(duì)象時(shí),會(huì)對(duì)Executor對(duì)象加上一個(gè)裝飾者:CachingExecutor,這時(shí)SqlSession使用CachingExecutor對(duì)象來(lái)完成操作請(qǐng)求。CachingExecutor對(duì)于查詢(xún)請(qǐng)求,會(huì)先判斷該查詢(xún)請(qǐng)求在Application級(jí)別的二級(jí)緩存中是否有緩存結(jié)果,如果有查詢(xún)結(jié)果,則直接返回緩存結(jié)果,如果緩存中沒(méi)有,再交給真正的Executor對(duì)象來(lái)完成查詢(xún)操作,之后CachingExecutor會(huì)將真正Executor返回的查詢(xún)結(jié)果放置到緩存中,然后再返回給用戶(hù)
CachingExecutor是Executor的裝飾者,以增強(qiáng)Executor的功能,使其具有緩存查詢(xún)的功能
MyBatis自身提供了豐富的,并且功能強(qiáng)大的二級(jí)緩存的實(shí)現(xiàn),它擁有一系列的Cache接口裝飾者,可以滿(mǎn)足各種對(duì)緩存操作和更新的策略。MyBatis定義了大量的Cache的裝飾器來(lái)增強(qiáng)Cache緩存的功能
參考1:終結(jié)篇:MyBatis原理深入解析(三)
參考2:JAVA設(shè)計(jì)模式初探之裝飾者模式
參考3:設(shè)計(jì)模式詳解——裝飾者模式