網(wǎng)站源碼推薦谷歌sem
個(gè)人主頁:金鱗踏雨
個(gè)人簡介:大家好,我是金鱗,一個(gè)初出茅廬的Java小白
目前狀況:22屆普通本科畢業(yè)生,幾經(jīng)波折了,現(xiàn)在任職于一家國內(nèi)大型知名日化公司,從事Java開發(fā)工作
我的博客:這里是CSDN,是我學(xué)習(xí)技術(shù),總結(jié)知識(shí)的地方。希望和各位大佬交流,共同進(jìn)步 ~
使用組合替代繼承,對原生對象的方法做增強(qiáng),增加新的行為和能力。
一、實(shí)現(xiàn)原理
裝飾器設(shè)計(jì)模式(Decorator)是一種結(jié)構(gòu)型設(shè)計(jì)模式,它允許動(dòng)態(tài)地為對象添加新的行為。它通過創(chuàng)建一個(gè)包裝器來實(shí)現(xiàn),即將對象放入一個(gè)裝飾器類中,再將裝飾器類放入另一個(gè)裝飾器類中,以此類推,形成一條包裝鏈。
在不改變原有對象的情況下,動(dòng)態(tài)地添加新的行為或修改原有行為。
代碼實(shí)現(xiàn)
1、定義一個(gè)接口或抽象類,作為被裝飾對象的基類。
public interface Component {void operation();
}
在這個(gè)示例中,我們定義了一個(gè)名為 Component 的接口,它包含一個(gè)名為 operation 的抽象方法,用于定義被裝飾對象的基本行為。
2、定義一個(gè)具體的被裝飾對象,實(shí)現(xiàn)基類中的方法。
public class ConcreteComponent implements Component {@Overridepublic void operation() {System.out.println("ConcreteComponent is doing something...");}
}
在這個(gè)示例中,我們定義了一個(gè)名為 ConcreteComponent 的具體實(shí)現(xiàn)類,實(shí)現(xiàn)了 Component 接口中的 operation 方法。
3、定義一個(gè)抽象裝飾器類,繼承基類,并將被裝飾對象作為屬性。
public abstract class Decorator implements Component {// 裝飾器設(shè)計(jì)模式 使用組合的形式進(jìn)行裝飾protected Component component;public Decorator(Component component) {this.component = component;}@Overridepublic void operation() {component.operation();}
}
在這個(gè)示例中,我們定義了一個(gè)名為 Decorator 的抽象類,繼承了 Component 接口,并將被裝飾對象作為屬性。在 operation 方法中,我們調(diào)用被裝飾對象的同名方法。
4、定義具體的裝飾器類,繼承抽象裝飾器類,并實(shí)現(xiàn)增強(qiáng)邏輯。
public class ConcreteDecoratorA extends Decorator {public ConcreteDecoratorA(Component component) {System.out.println("這是第一次包裝...");super(component);}@Overridepublic void operation() {super.operation();System.out.println("ConcreteDecoratorA is adding new behavior...");}
}public class ConcreteDecoratorB extends Decorator {public ConcreteDecoratorB(Component component) {System.out.println("這是第二次包裝...");super(component);}@Overridepublic void operation() {super.operation();System.out.println("ConcreteDecoratorB is adding new behavior...");}
}
在這個(gè)示例中,我們定義了一個(gè)名為 ConcreteDecoratorA 的具體裝飾器類,繼承了 Decorator 抽象類,并實(shí)現(xiàn)了 operation 方法的增強(qiáng)邏輯。在 operation 方法中,我們先調(diào)用被裝飾對象的同名方法,然后添加新的行為。
5、使用裝飾器增強(qiáng)被裝飾對象。
public class Main {public static void main(String[] args) {Component component = new ConcreteComponent();// 進(jìn)行第一次包裝component = new ConcreteDecoratorA(component);// 進(jìn)行第二次包裝component = new ConcreteDecoratorB(component);component.operation();}
}
在這個(gè)示例中,我們先創(chuàng)建了一個(gè)被裝飾對象 ConcreteComponent,然后通過 ConcreteDecoratorA 類創(chuàng)建了一個(gè)裝飾器,并將被裝飾對象作為參數(shù)傳入。最后,調(diào)用裝飾器的 operation 方法,這樣就可以實(shí)現(xiàn)對被裝飾對象的增強(qiáng)。
裝飾器設(shè)計(jì)模式,包裝后可以繼續(xù)包裝...類似于套娃。一層一層,逐步增強(qiáng)!!!
裝飾器與靜態(tài)代理的區(qū)別
裝飾器模式和靜態(tài)代理模式最大的區(qū)別——目的不同!
代理模式的目的是為了控制對對象的訪問,它在對象的外部提供一個(gè)代理對象來控制對原始對象的訪問。代理對象和原始對象通常實(shí)現(xiàn)同一個(gè)接口或繼承同一個(gè)類,以保證二者可以互相替代。
裝飾器模式的目的是為了動(dòng)態(tài)地增強(qiáng)對象的功能,它在對象的內(nèi)部通過一種包裝器的方式來實(shí)現(xiàn)。裝飾器模式中,裝飾器類和被裝飾對象通常實(shí)現(xiàn)同一個(gè)接口或繼承同一個(gè)類,以保證二者可以互相替代。裝飾器模式也被稱為包裝器模式。
需要注意的是,裝飾器模式雖然可以實(shí)現(xiàn)動(dòng)態(tài)地為對象增加行為,但是會(huì)增加系統(tǒng)的復(fù)雜性,因此在使用時(shí)需要仔細(xì)權(quán)衡利弊。
二、使用場景
在 Java 中,裝飾器模式的應(yīng)用非常廣泛,特別是在 I/O 操作中。Java 中的 I/O 類庫就是使用裝飾器模式來實(shí)現(xiàn)不同的數(shù)據(jù)流之間的轉(zhuǎn)換和增強(qiáng)的。
1.從IO庫的設(shè)計(jì)理解裝飾器
打開文件 test.txt,從中讀取數(shù)據(jù)。
其中,InputStream 是一個(gè)抽象類,FileInputStream 是專門用來讀取文件流的子類。BufferedInputStream 是一個(gè)支持帶緩存功能的數(shù)據(jù)讀取類,可以提高數(shù)據(jù)讀取的效率
InputStream in = new FileInputStream("D:/test.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {//...
}
初看上面的代碼,我們會(huì)覺得 Java IO 的用法比較麻煩,需要先創(chuàng)建一個(gè) FileInputStream 對象,然后再傳遞給 BufferedInputStream 對象來使用。
為什么Java IO 為什么不設(shè)計(jì)一個(gè)繼承 FileInputStream 并且支持緩存的 BufferedFileInputStream 類呢?
這樣直接創(chuàng)建一個(gè) BufferedFileInputStream 類對象,打開文件讀取數(shù)據(jù),用起來豈不是更加簡單?
InputStream bin = new BufferedFileInputStream("/user/wangzheng/test.txt");
byte[] data = new byte[128];
while (bin.read(data) != -1) {//...
}
(1)基于繼承的設(shè)計(jì)方案(能實(shí)現(xiàn),不靠譜)
如果 InputStream 只有一個(gè)子類 FileInputStream 的話,那我們在 FileInputStream 基礎(chǔ)之上,再設(shè)計(jì)一個(gè)孫子類 BufferedFileInputStream,這樣子是完全可以的,畢竟繼承結(jié)構(gòu)還算簡單!
但實(shí)際上,繼承 InputStream 的子類有很多。我們需要給每一個(gè) InputStream 的子類,再繼續(xù)派生支持緩存讀取的子類。
除了支持緩存讀取之外,如果我們還需要對功能進(jìn)行其他方面的增強(qiáng),比如下面的 DataInputStream 類,支持按照基本數(shù)據(jù)類型(int、boolean、long 等)來讀取數(shù)據(jù)。
FileInputStream in = new FileInputStream("/user/wangzheng/test.txt");
DataInputStream din = new DataInputStream(in);
int data = din.readInt();
在這種情況下,如果我們繼續(xù)按照繼承的方式來實(shí)現(xiàn)的話,就需要再繼續(xù)派生出 DataFileInputStream、DataPipedInputStream 等類。如果我們還需要既支持緩存、又支持按照基本類型讀取數(shù)據(jù)的類,那就要再繼續(xù)派生出 BufferedDataFileInputStream、BufferedDataPipedInputStream 等 n 多類。
這還只是附加了兩個(gè)增強(qiáng)功能,如果我們需要附加更多的增強(qiáng)功能,那就會(huì)導(dǎo)致組合爆炸,類繼承結(jié)構(gòu)變得無比復(fù)雜,代碼既不好擴(kuò)展,也不好維護(hù)。
(2)基于裝飾器模式的設(shè)計(jì)方案
組合優(yōu)于繼承,建議使用組合來替代繼承!!!
針對剛剛的繼承結(jié)構(gòu)過于復(fù)雜的問題,我們可以通過將繼承關(guān)系改為組合關(guān)系來解決。
public abstract class InputStream {//...public int read(byte b[]) throws IOException {return read(b, 0, b.length);}public int read(byte b[], int off, int len) throws IOException {//...}public long skip(long n) throws IOException {//...}public int available() throws IOException {return 0;}public void close() throws IOException {}public synchronized void mark(int readlimit) {}public synchronized void reset() throws IOException {throw new IOException("mark/reset not supported");}public boolean markSupported() {return false;}
}public class BufferedInputStream extends InputStream {protected volatile InputStream in;protected BufferedInputStream(InputStream in) {this.in = in;}//...實(shí)現(xiàn)基于緩存的讀數(shù)據(jù)接口...
}public class DataInputStream extends InputStream {protected volatile InputStream in;protected DataInputStream(InputStream in) {this.in = in;}//...實(shí)現(xiàn)讀取基本類型數(shù)據(jù)的接口
}
那裝飾器模式就是簡單的“用組合替代繼承”嗎?
當(dāng)然不是!從 Java IO 的設(shè)計(jì)來看,裝飾器模式相對于簡單的組合關(guān)系,還有兩個(gè)比較特殊的地方!
1、裝飾器類和原始類繼承同樣的父類,這樣我們可以對原始類“嵌套”多個(gè)裝飾器類。
比如,下面這樣一段代碼,我們對 FileInputStream 嵌套了兩個(gè)裝飾器類:BufferedInputStream 和 DataInputStream,讓它既支持緩存讀取,又支持按照基本數(shù)據(jù)類型來讀取數(shù)據(jù)。
// 逐次增強(qiáng)!
InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();
2、裝飾器類是對功能的增強(qiáng),這也是裝飾器模式應(yīng)用場景的一個(gè)重要特點(diǎn)。
實(shí)際上,符合“組合關(guān)系”這種代碼結(jié)構(gòu)的設(shè)計(jì)模式有很多,比如之前講過的代理模式,還有現(xiàn)在的裝飾器模式。盡管它們的代碼結(jié)構(gòu)很相似,但是每種設(shè)計(jì)模式的意圖是不同的!!!
例如,代理模式中,代理類附加的是跟原始類無關(guān)的功能,而在裝飾器模式中,裝飾器類附加的是跟原始類相關(guān)的增強(qiáng)功能。
// 代理模式的代碼結(jié)構(gòu)(下面的接口也可以替換成抽象類)
public interface IA {void f();
}
public class A impelements IA {public void f() { //... }
}
public class AProxy impements IA {private IA a;public AProxy(IA a) {this.a = a;}public void f() {// 新添加的代理邏輯a.f();// 新添加的代理邏輯}
}// 裝飾器模式的代碼結(jié)構(gòu)(下面的接口也可以替換成抽象類)
public interface IA {void f();
}
public class A impelements IA {public void f() {// ...}
}
public class ADecorator impements IA {private IA a;public ADecorator(IA a) {this.a = a;}public void f() {// 功能增強(qiáng)代碼a.f();// 功能增強(qiáng)代碼}
}
BufferedInputStream為什么不直接繼承InputStream?
實(shí)際上,如果去查看 JDK 的源碼,你會(huì)發(fā)現(xiàn),BufferedInputStream、DataInputStream 并非繼承自 InputStream,而是另外一個(gè)叫 FilterInputStream 的類。那這又是出于什么樣的設(shè)計(jì)意圖,才引入這樣一個(gè)類呢?
InputStream 是一個(gè)抽象類而非接口,而且它的大部分函數(shù)(比如 read()、available())都有默認(rèn)實(shí)現(xiàn),按理來說,我們只需要在 BufferedInputStream 類中重新實(shí)現(xiàn)那些需要增加緩存功能的函數(shù)就可以了,其他函數(shù)繼承 InputStream 的默認(rèn)實(shí)現(xiàn)。但實(shí)際上,這樣做是行不通的!
對于即便是不需要增加緩存功能的函數(shù)來說,BufferedInputStream 還是必須把它重新實(shí)現(xiàn)一遍,簡單包裹對 InputStream 對象的函數(shù)調(diào)用;如果不重新實(shí)現(xiàn),那 BufferedInputStream 類就無法將最終讀取數(shù)據(jù)的任務(wù),委托給傳遞進(jìn)來的 InputStream 對象來完成。
public class BufferedInputStream extends InputStream {protected volatile InputStream in;protected BufferedInputStream(InputStream in) {this.in = in;}// f()函數(shù)不需要增強(qiáng),只是重新調(diào)用一下InputStream in對象的f()public void f() {in.f();}
}
實(shí)際上,DataInputStream 也存在跟 BufferedInputStream 同樣的問題。為了避免代碼重復(fù),Java IO 抽象出了一個(gè)裝飾器父類 FilterInputStream。
InputStream 的所有的裝飾器類(BufferedInputStream、DataInputStream)都繼承自這個(gè)裝飾器父類。這樣,裝飾器類只需要實(shí)現(xiàn)它需要增強(qiáng)的方法就可以了,其他方法繼承裝飾器父類的默認(rèn)實(shí)現(xiàn)。
public class FilterInputStream extends InputStream {protected volatile InputStream in;protected FilterInputStream(InputStream in) {this.in = in;}public int read() throws IOException {return in.read();}public int read(byte b[]) throws IOException {return read(b, 0, b.length);}public int read(byte b[], int off, int len) throws IOException {return in.read(b, off, len);}public long skip(long n) throws IOException {return in.skip(n);}public int available() throws IOException {return in.available();}public void close() throws IOException {in.close();}public synchronized void mark(int readlimit) {in.mark(readlimit);}public synchronized void reset() throws IOException {in.reset();}public boolean markSupported() {return in.markSupported();}
}
總的來說,當(dāng) BufferedInputStream 繼承自 FilterInputStream 時(shí),它可以非常輕松地?cái)U(kuò)展 FilterInputStream 的行為,從而實(shí)現(xiàn)了輸入流的緩沖功能。如果 BufferedInputStream 直接繼承自 InputStream,那么它就需要重新實(shí)現(xiàn)所有 InputStream 的方法,包括一些可能不需要修改的方法,這會(huì)使代碼變得復(fù)雜且容易出錯(cuò)。
2.MyBatis的緩存設(shè)計(jì)
創(chuàng)建緩存的過程
useNewCache方法用于配置Mapper是否使用一個(gè)全新的緩存實(shí)例,而不共享緩存實(shí)例
public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {Cache cache = new CacheBuilder(currentNamespace)// 根據(jù)類型生成實(shí)例,并進(jìn)行配置.implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)) // 添加裝飾器.clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache;
}
默認(rèn)的緩存如下,本質(zhì)就是維護(hù)了一個(gè)簡單的HashMap
public class PerpetualCache implements Cache {private final String id;private final Map<Object, Object> cache = new HashMap<>();public PerpetualCache(String id) {this.id = id;}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}// ...省略其他的簡單的方法}
緩存的構(gòu)建過程
public Cache build() {// 設(shè)置默認(rèn)的cache實(shí)現(xiàn),并綁定默認(rèn)的淘汰策略setDefaultImplementations();// 利用反射創(chuàng)建實(shí)例Cache cache = newBaseCacheInstance(implementation, id);// 設(shè)置properties屬性setCacheProperties(cache);// 不應(yīng)用裝飾自定義緩存,自定義緩存需要自己實(shí)現(xiàn)對應(yīng)的特性,如淘汰策略等// 通常情況自定義緩存有自己的獨(dú)立配置,如redis、ehcacheif (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}// 這是標(biāo)準(zhǔn)的裝飾器,這里使用了裝飾器設(shè)計(jì)模式cache = setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache;
}
MyBatis會(huì)使用裝飾者設(shè)計(jì)模式,對默認(rèn)cache進(jìn)行裝飾,使其具有LRU的能力
private void setDefaultImplementations() {if (implementation == null) {implementation = PerpetualCache.class;if (decorators.isEmpty()) {// decorators是成員變量,裝飾器,飾器具備LRU的能力decorators.add(LruCache.class);}}
}
LruCache實(shí)現(xiàn)如下,默認(rèn)情況下的LRU算法實(shí)現(xiàn)是基于LinkedHashMap實(shí)現(xiàn)的
public class LruCache implements Cache {// 代理目標(biāo)緩存private final Cache delegate;private Map<Object, Object> keyMap;private Object eldestKey;// LruCache用來裝飾默認(rèn)的緩存,這里實(shí)現(xiàn)了緩存的高級特性public LruCache(Cache delegate) {this.delegate = delegate;setSize(1024);}@Overridepublic String getId() {return delegate.getId();}@Overridepublic int getSize() {return delegate.getSize();}// 設(shè)置長度,構(gòu)建一個(gè)LinkedHashMap,重寫removeEldestEntrypublic void setSize(final int size) {// 第三個(gè)參數(shù)accessOrder為true,可以使LinkedHashMap維護(hù)一個(gè)【訪問順序】// 最近被訪問的數(shù)據(jù)會(huì)被放在鏈表的尾部,天然實(shí)現(xiàn)lrukeyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {private static final long serialVersionUID = 4267176411845948333L;// 重寫該方法,父類直接返回false// 只要實(shí)際容量size() 大于 初始化容量 size 認(rèn)定當(dāng)前的緩存已經(jīng)滿了// 該方法會(huì)在LinkedHashMap的afterNodeInsertion方法中被主動(dòng)調(diào)用// 會(huì)將頭節(jié)點(diǎn)當(dāng)作eldest刪除@Overrideprotected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {boolean tooBig = size() > size;if (tooBig) {// 同時(shí)將這個(gè)這個(gè)key復(fù)制給成員變量eldestKeyeldestKey = eldest.getKey();}return tooBig;}};}// put一個(gè)緩存的過程// 放入當(dāng)前的緩存值,淘汰eldestKey@Overridepublic void putObject(Object key, Object value) {delegate.putObject(key, value);cycleKeyList(key);}// get一個(gè)緩存的過程// 獲得該值,同時(shí)提升key熱度,主動(dòng)訪問一下keyMap.get(key)@Overridepublic Object getObject(Object key) {keyMap.get(key); return delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyMap.clear();}// 循環(huán)key的集合private void cycleKeyList(Object key) {keyMap.put(key, key);if (eldestKey != null) {delegate.removeObject(eldestKey);eldestKey = null;}}}
最后使用其他的裝飾器對cache進(jìn)行裝飾,使其就有更多的能力
private Cache setStandardDecorators(Cache cache) {try {MetaObject metaCache = SystemMetaObject.forObject(cache);// 設(shè)置大小,默認(rèn)1024if (size != null && metaCache.hasSetter("size")) {metaCache.setValue("size", size);}if (clearInterval != null) {cache = new ScheduledCache(cache);((ScheduledCache) cache).setClearInterval(clearInterval);}if (readWrite) {cache = new SerializedCache(cache);}cache = new LoggingCache(cache);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);}
}
三、總結(jié)
裝飾器模式主要解決繼承關(guān)系過于復(fù)雜的問題,通常是通過組合來替代繼承。
它主要的作用是給原始類添加功能。這也是判斷是否該用裝飾器模式的一個(gè)重要的依據(jù)。除此之外,裝飾器模式還有一個(gè)特點(diǎn),那就是可以對原始類嵌套使用多個(gè)裝飾器。為了滿足這個(gè)應(yīng)用場景,在設(shè)計(jì)的時(shí)候,裝飾器類需要跟原始類繼承相同的抽象類或者接口。
Q: 在學(xué)習(xí)代理設(shè)計(jì)模式的時(shí)候,我們通過裝飾者模式給 InputStream 添加緩存讀取數(shù)據(jù)功能。那對于“添加緩存”這個(gè)應(yīng)用場景來說,我們到底是該用代理模式還是裝飾器模式呢?你怎么看待這個(gè)問題?
事實(shí)上對于絕大多數(shù)的"添加緩存"的業(yè)務(wù)場景,核心目的主要就是想增強(qiáng)對象的功能(即增加緩存功能),而并不是控制對對象的訪問,所以裝飾器模式可能會(huì)更合適。但是假如,我們想強(qiáng)制對持久層增加一個(gè)本地緩存,代理設(shè)計(jì)模式也是很好的選擇。
文章到這里就結(jié)束了,如果有什么疑問的地方,可以在評論區(qū)指出~
希望能和大佬們一起努力,諸君頂峰相見
再次感謝各位小伙伴兒們的支持!!!