通過修改代碼給已有的類型增加功能是最直接的方式。 但往往有很多原因讓這種方式被重新考慮:
- JDK或者第三方類庫,無論如何你都要考慮其他方式,而不是直接修改。
- 新增的職責不適合被添加到現有的任何一個類型中。
通過繼承在很多情況下是個容易想到的方式。比如,增加子類來獲得新的類型,在新的子類當中可以增加很妙的新功能。假如這個功能果實在很妙,於是其它已有的子類(兄弟)也希望獲得這樣的功能,問題就出現了:
- 爲了讓所有的兄弟類型也擁有這樣的功能,要麼修改兄弟類型(違反開閉原則和麪臨直接修改的問題), 要麼通過子類來擴展。
- 這個新功能如果會發生獨立的變化,這種變化傳遞到已有的代碼中,使已有類型變化維度增加,子類數量快速膨脹。
- 除了繼承的父類不同,新增的功能代碼完全相同。 既然如此,這些子類之間存在着重複的代碼(邏輯或數據)。 對於重複的代碼,我們一般想到的是把代碼塊抽取成方法,並通過傳遞參數來調用。 我們這些繼承的子類之間,父類是唯一的不同,因此考慮把父類對象參數來傳遞。
- 如果把繼承體系中的父類抽出去,失去繼承,所有的父類功能也就無法得到繼承。 但此時我們決定把父類對象當做一個參數傳遞到子類,通過這個對象,可以把不能從父類繼承的功能通過組合的方式得到。 由於不是通過繼承,但擁有父類的功能,爲此可以給它們指定相同的接口。
這種設計就是Decorator模式,可以動態的給已有的對象增加功能。Java JDK當中,Decorator模式比比皆是,我這裏摘取其中的一個例子:
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
//省略其他的功能
}
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
//省略其他的功能
}
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in) {
super(in);
}
//從這裏開始,增加或者修改功能
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
}
InputStream 是一個輸入流的抽象類,它的各種子類僅支持基本的read功能。 對於文件的讀取添加buffer的功能會大大提高效率, 因此希望這個獨立的功能被應用到所有的InputStream當中。爲了獲得這個功能,Java JDK並不是通過修改已有的InputStream,而是通過一個新的繼承類BufferedInputStream來完成這個新的職責。 這樣也符合開閉原則。
BufferedInputStream在構造實例時,把InputStream實例作爲構造函數的一個參數,如此,BufferedInputStream的實例就可以擁有了InputStream實例的具體功能(這些職責是由父類FilterInputStream來承擔的,這樣BufferedInputStream就可以專心做自己的本職工作) 。 換句話說InputStream實例獲得BufferedInputStream類型當中的新功能。
有一點不太明白,FilterInputStream難道就真的是在幫BufferedInputSream完成繼承InputStream的功能嗎?額外添加這樣一個類,是否值得?