給對象添加功能

通過修改代碼給已有的類型增加功能是最直接的方式。 但往往有很多原因讓這種方式被重新考慮:

  • 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的功能嗎?額外添加這樣一個類,是否值得? 







發佈了21 篇原創文章 · 獲贊 12 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章