论面向组合子程序设计方法 之 重构2
生活随笔
收集整理的這篇文章主要介紹了
论面向组合子程序设计方法 之 重构2
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
已經有點感覺用ioc container來說明co不見得是個好主意了。?
這個container的例子舉出來,明顯提出意見的人比那個簡單的logging例子少了很多。?
畢竟連pico是怎么回事,怎么用,很多人都還不見得了了。更不提多少人對pico的用法就是一個很in的fancy factory。買櫝還珠。?
不過,既然開始了,讓我還是有始有終吧。?
這章還是讓我們看看co的refactor。?
其實,很多人問:怎樣把握co里面的基本組合子的度;什么樣的組合子算是基本;怎樣做到正交;多少的基本組合子才算夠用;怎么知道這個組合子會被用到等等。?
其實,答案都來自重構。?
沒有誰一下子就作對的。co比起oo,我感覺在設計上反而更容易避免過度設計。?
為什么??
設計oo的時候,你要分析需求,設計各個模塊的通信接口,這個過程,同樣需要經驗,同樣需要摸索,同樣沒有一踀而就的捷徑。?
但是,oo設計的時候又要避免過度,一些時候,在是否通過接口預留靈活性,提取容易變化的部分,或者是盡量簡單之間,還是有沖突的。你需要做一個艱難的猜測和抉擇。?
而一旦抉擇作出,以后如果發現事情進展不如所愿,那么改動接口的代價相當的大。?
而如果使用co,在設計簡單的各個組合子的時候,你會以一種非常漸進式的方式來發現:哦,原來的組合子設計不夠正交,有這個地方可以抽出來,好,抽出來,把波及到的幾個組合子的設計修改一下。?
因為組合子都非常簡單,這個變化的波及范圍一般來說相當小。?
好,空話少說,我們還是看具體例子。?
現在,我們發現,除了withArgument, withProperty,我們還希望更靈活地設置參數,比如,我們希望說:?
[list]對組件X的各個參數,類型為A的,選取以"a1"標識的組件作為參數值,其它的按照缺省方式。?
對組件Y的各個參數,類型為A的,選取以"a2"標識的組件為參數值,其它的按照缺省方式。[/list:u]?
這個需求有幾個點:?
[list]1。需要能夠通過key來直接指定某個某個組件,相當于一個"ref"。?
2。需要對參數配置有除了按照參數位置之外的更靈活的配置(比如,按照參數類型)。[/list:u]?
對第一點,我們制作以下的組合子來對應。(看,我們是可以隨著需求雖然豐富我們的基本組合子的集合的)?
我們期望做一個UseKey組合子,它可以從容器里面取得另外一個用某個key標識的組件,然后把一切動作都delegate過去。?
Java代碼??class?UseKey?extends?Component{?? ??private?final?Object?key;?? ??public?Object?create(Dependency?dep);{?? ????//??????????? ??}?? ??....?? }??
可是,一開始寫代碼,就發現,這個代碼寫不下去!我們需要得到這個容器,才能從這個容器里面取得那個要delegate的組件。可是這個可愛容器對象在哪里呀??
仔細分析下來,發現,沒有辦法。唯一的辦法是修改Dependency接口,讓它除了幫助解析參數和property之外,再提供給我們當前容器的信息。?
Dependency接口變為:?
Java代碼??interface?Dependency{?? ??Object?getArgument(int?i,?Class?type);;?? ??Object?getProperty(Object?key,?Class?type);;?? ??Container?getContainer();;?? }??
Wow!要改接口了!其實,這一點也不可怕。為什么??
co還有另外一個優點我們一直沒有提及:細節封裝。這個封裝不是一般OO意義上的封裝,而是說:把要實現的接口細節封裝起來,讓客戶通過預定義好的組合方式來擴展,而不是象oo那樣讓用戶實現實現這個接口來擴展。?
其實,如果用戶使用的都是Component對象,而創建Component對象都是通過:?
Java代碼??Container.getInstance(Object?key);;??
這種方式,那么,Dependency這個接口已經實際上淪為我們的內部實現細節了。用戶根本不需要知道存在這么一個接口。?
實際上,當我們的組合子足夠豐富之后,完全可以把Dependency接口隱藏在包內部,徹底地對用戶屏蔽這個接口。?
如此,客戶的擴展完全通過組合Component對象,而不是實現Component接口并且調用Dependency接口。?
不管這個Dependency接口是如何設計的,如何變化,我們都可以把變化隔離在我們包內部,而不會影響用戶。?
好吧。現在假設我們修改了Dependency接口,那么UseKey可以被寫為:?
Java代碼??class?UseKey?extends?Component{?? ??private?final?Object?key;?? ??public?Object?create(Dependency?dep);{?? ?????final?Component?c?=?dep.getContainer();.getComponent(key);;?? ?????if(c==null);throw?new?ComponentNotFoundException(...);;?? ?????return?c.create(dep);;?? ??}?? ??....?? }??
然后,更靈活的參數配置。對這個,我們可以借鑒bind操作,做一個對參數的bind。?
Java代碼??interface?ParameterBinder{?? ??Component?bind(int?i,?Class?type);;?? }??
不知道你從Binder接口和ParameterBinder接口看出點什么沒有??
1。Binder, ParameterBinder接口都是給用戶去實現的。?
2。這兩個接口都不暴露Component的細節,它們的參數和返回值都不涉及Component的接口簽名,客戶在實現這兩個接口的時候,完全不必關心象Dependency接口這種細節。?
3。返回值都是Component,這樣,所有的Component組合子都可以被自由使用。?
實際上,monad組合子就是通過這種方式來在高階邏輯的層次上隱藏底層細節。?
Java代碼??class?ParameterBoundDependency?implements?Dependency{?? ??private?final?Dependency?dep;?? ??private?final?ParameterBinder?binder;?? ??public?Object?getArgument(int?i,?Class?type);{?? ????return?binder.bind(i,?type);.create(dep);;?? ??}?? ??...?? }??
Java代碼??ParameterBoundComponent?extends?Component{?? ??private?final?Component?c;?? ??private?final?ParameterBinder?binder;?? ??public?Object?create(Dependency?dep);{?? ????return?c.create(new?ParameterBoundDependency(dep,?binder););;?? ??}?? ??...?? }??
用ParameterBinder來做一個Dependency的decorator,問題得到了解決。?
然后我們來使用ParameterBoundComponent,為了書寫簡便,我們假設Component類有一個函數叫做bind(ParameterBinder binder)。另外Components類有一個useKey(Object key)函數來生成一個Component對象,用來指向容器內的另外一個組件。?
于是,上面的需求被實現為:?
Java代碼??Component?x?=?...;?? Component?x2?=?x.bind(new?ParameterBinder();{?? ??public?Component?bind(int?i,?Class?type);{?? ????if(type.equals(A.class););{?? ??????return?Components.useKey("a1");;?? ????}?? ????else{?? ??????//?????行1?? ????}?? ??}?? });;??
這個x2組件,就是為了實現“當參數類型為A,使用a1,否則使用缺省方式”。?
可是,在行1處,再次遇到了障礙。這個所謂的“缺省方式”,怎么表示??
。?
經過思考,我們決定實現一個useArgument(int i, Class type)這樣一個組合子,這個組合子可以主動在當前的Dependency對象中選擇某個參數作為自己的值。這樣,上面的行1就可以寫作:?
Java代碼??Component?x?=?...;?? Component?x2?=?x.bind(new?ParameterBinder();{?? ??public?Component?bind(int?i,?Class?type);{?? ????if(type.equals(A.class););{?? ??????return?Components.useKey("a1");;?? ????}?? ????else{?? ??????return?Components.useArgument(i,?type);;//?行1?? ????}?? ??}?? });;??
下面來實現一個UseArgument類:?
Java代碼??class?UseArgument?extends?Component{?? ??private?final?int?i;?? ??private?final?Class?type;?? ??public?Object?create(Dependency?dep);{?? ????return?dep.getArgument(i,?type);;?? ??}?? ??.....?? }??
哈。完美。一切仍然盡在掌握。?
我們可以以幾乎任何方式來customizer組件的參數和property。?
實際上,如果我們回頭看看,甚至可以發現,withArgument(int i, Class type)完全可以用bind(ParameterBinder)來重寫:?
Java代碼??Component?withArgument(Component?c,?final?int?i,?final?Component?arg);{?? ??return?c.bind(new?ParameterBinder();{?? ????public?Component?bind(int?k,?Class?type);{?? ??????if(k==i);?return?arg;?? ??????else?return?Components.useArgument(k,?type);;?? ????}?? ??});;?? }??
我們很開心地看到,原來的WithArgument類,WithProperty類都可以掃進垃圾箱了。我們只需要實現更加簡單的ParameterBinder接口就可以搞定一切。哈。?
同時,希望你也看到了隱藏這些具體的WithArgument,ValueComponent類,而用靜態工廠函數withArgument(), value()來代替的好處:?
我們可以自由地重構。當發現某個組合子本身并非最簡單,而是可以從一些更簡單的組合子推演出來,我們只需要改動這些靜態工廠函數,而不必告訴用戶:對不起,我的設計改了,不想要WithArgument類了,你能不能改改你的那段new WithArgument(...)的代碼??
co讓用戶只關注接口,而不要管某個功能是直接實現的,還是組合出來的。靜態工廠函數提供了對這個細節的封裝。?
另外一個也許會比較常見的需求,是用一個數組來一次性指定某個組件的所有參數,比如:?
Java代碼??c.withArguments(new?Component[]{c1,?c2,?c3});;??
這個功能用bind非常非常好實現:?
Java代碼??Component?withArguments(final?Component[]?args);{?? ??return?bind(new?ParameterBinder();{?? ????public?Component?bind(int?i,?Class?type);{?? ??????return?args[i];?? ????}?? ??});;?? }??
當然,你還可以舉一反三地提出很多其它的定制參數和property的方法。?
好了。今天就到這里。在結束前,我來先提出兩個新的需求:?
1。希望對一些用到Logger對象的類注射Logger實例,而這個Logger實例需要用這個使用Logger對象的類對象來創建,這樣,這個Logger對象可以靜態地知道誰在使用它,而不必每次都構造一個異常來取得StackTrace。?
比如,?
Java代碼??new?ClassX(...,?Loggers.instance(ClassX.class);,?...);;??
怎樣在容器級別全局地規定這個規則呢?我們不知道哪些組件需要注射Logger,也不知道這些組件在哪個參數注射Logger對象。?
2。怎樣提供缺省參數?這樣,如果某個參數的需要可以在容器中解析,則擁這個解析出來的實例,否則,使用一個缺省組件。?
這個container的例子舉出來,明顯提出意見的人比那個簡單的logging例子少了很多。?
畢竟連pico是怎么回事,怎么用,很多人都還不見得了了。更不提多少人對pico的用法就是一個很in的fancy factory。買櫝還珠。?
不過,既然開始了,讓我還是有始有終吧。?
這章還是讓我們看看co的refactor。?
其實,很多人問:怎樣把握co里面的基本組合子的度;什么樣的組合子算是基本;怎樣做到正交;多少的基本組合子才算夠用;怎么知道這個組合子會被用到等等。?
其實,答案都來自重構。?
沒有誰一下子就作對的。co比起oo,我感覺在設計上反而更容易避免過度設計。?
為什么??
設計oo的時候,你要分析需求,設計各個模塊的通信接口,這個過程,同樣需要經驗,同樣需要摸索,同樣沒有一踀而就的捷徑。?
但是,oo設計的時候又要避免過度,一些時候,在是否通過接口預留靈活性,提取容易變化的部分,或者是盡量簡單之間,還是有沖突的。你需要做一個艱難的猜測和抉擇。?
而一旦抉擇作出,以后如果發現事情進展不如所愿,那么改動接口的代價相當的大。?
而如果使用co,在設計簡單的各個組合子的時候,你會以一種非常漸進式的方式來發現:哦,原來的組合子設計不夠正交,有這個地方可以抽出來,好,抽出來,把波及到的幾個組合子的設計修改一下。?
因為組合子都非常簡單,這個變化的波及范圍一般來說相當小。?
好,空話少說,我們還是看具體例子。?
現在,我們發現,除了withArgument, withProperty,我們還希望更靈活地設置參數,比如,我們希望說:?
[list]對組件X的各個參數,類型為A的,選取以"a1"標識的組件作為參數值,其它的按照缺省方式。?
對組件Y的各個參數,類型為A的,選取以"a2"標識的組件為參數值,其它的按照缺省方式。[/list:u]?
這個需求有幾個點:?
[list]1。需要能夠通過key來直接指定某個某個組件,相當于一個"ref"。?
2。需要對參數配置有除了按照參數位置之外的更靈活的配置(比如,按照參數類型)。[/list:u]?
對第一點,我們制作以下的組合子來對應。(看,我們是可以隨著需求雖然豐富我們的基本組合子的集合的)?
我們期望做一個UseKey組合子,它可以從容器里面取得另外一個用某個key標識的組件,然后把一切動作都delegate過去。?
Java代碼??
可是,一開始寫代碼,就發現,這個代碼寫不下去!我們需要得到這個容器,才能從這個容器里面取得那個要delegate的組件。可是這個可愛容器對象在哪里呀??
仔細分析下來,發現,沒有辦法。唯一的辦法是修改Dependency接口,讓它除了幫助解析參數和property之外,再提供給我們當前容器的信息。?
Dependency接口變為:?
Java代碼??
Wow!要改接口了!其實,這一點也不可怕。為什么??
co還有另外一個優點我們一直沒有提及:細節封裝。這個封裝不是一般OO意義上的封裝,而是說:把要實現的接口細節封裝起來,讓客戶通過預定義好的組合方式來擴展,而不是象oo那樣讓用戶實現實現這個接口來擴展。?
其實,如果用戶使用的都是Component對象,而創建Component對象都是通過:?
Java代碼??
這種方式,那么,Dependency這個接口已經實際上淪為我們的內部實現細節了。用戶根本不需要知道存在這么一個接口。?
實際上,當我們的組合子足夠豐富之后,完全可以把Dependency接口隱藏在包內部,徹底地對用戶屏蔽這個接口。?
如此,客戶的擴展完全通過組合Component對象,而不是實現Component接口并且調用Dependency接口。?
不管這個Dependency接口是如何設計的,如何變化,我們都可以把變化隔離在我們包內部,而不會影響用戶。?
好吧。現在假設我們修改了Dependency接口,那么UseKey可以被寫為:?
Java代碼??
然后,更靈活的參數配置。對這個,我們可以借鑒bind操作,做一個對參數的bind。?
Java代碼??
不知道你從Binder接口和ParameterBinder接口看出點什么沒有??
1。Binder, ParameterBinder接口都是給用戶去實現的。?
2。這兩個接口都不暴露Component的細節,它們的參數和返回值都不涉及Component的接口簽名,客戶在實現這兩個接口的時候,完全不必關心象Dependency接口這種細節。?
3。返回值都是Component,這樣,所有的Component組合子都可以被自由使用。?
實際上,monad組合子就是通過這種方式來在高階邏輯的層次上隱藏底層細節。?
Java代碼??
Java代碼??
用ParameterBinder來做一個Dependency的decorator,問題得到了解決。?
然后我們來使用ParameterBoundComponent,為了書寫簡便,我們假設Component類有一個函數叫做bind(ParameterBinder binder)。另外Components類有一個useKey(Object key)函數來生成一個Component對象,用來指向容器內的另外一個組件。?
于是,上面的需求被實現為:?
Java代碼??
這個x2組件,就是為了實現“當參數類型為A,使用a1,否則使用缺省方式”。?
可是,在行1處,再次遇到了障礙。這個所謂的“缺省方式”,怎么表示??
。?
經過思考,我們決定實現一個useArgument(int i, Class type)這樣一個組合子,這個組合子可以主動在當前的Dependency對象中選擇某個參數作為自己的值。這樣,上面的行1就可以寫作:?
Java代碼??
下面來實現一個UseArgument類:?
Java代碼??
哈。完美。一切仍然盡在掌握。?
我們可以以幾乎任何方式來customizer組件的參數和property。?
實際上,如果我們回頭看看,甚至可以發現,withArgument(int i, Class type)完全可以用bind(ParameterBinder)來重寫:?
Java代碼??
我們很開心地看到,原來的WithArgument類,WithProperty類都可以掃進垃圾箱了。我們只需要實現更加簡單的ParameterBinder接口就可以搞定一切。哈。?
同時,希望你也看到了隱藏這些具體的WithArgument,ValueComponent類,而用靜態工廠函數withArgument(), value()來代替的好處:?
我們可以自由地重構。當發現某個組合子本身并非最簡單,而是可以從一些更簡單的組合子推演出來,我們只需要改動這些靜態工廠函數,而不必告訴用戶:對不起,我的設計改了,不想要WithArgument類了,你能不能改改你的那段new WithArgument(...)的代碼??
co讓用戶只關注接口,而不要管某個功能是直接實現的,還是組合出來的。靜態工廠函數提供了對這個細節的封裝。?
另外一個也許會比較常見的需求,是用一個數組來一次性指定某個組件的所有參數,比如:?
Java代碼??
這個功能用bind非常非常好實現:?
Java代碼??
當然,你還可以舉一反三地提出很多其它的定制參數和property的方法。?
好了。今天就到這里。在結束前,我來先提出兩個新的需求:?
1。希望對一些用到Logger對象的類注射Logger實例,而這個Logger實例需要用這個使用Logger對象的類對象來創建,這樣,這個Logger對象可以靜態地知道誰在使用它,而不必每次都構造一個異常來取得StackTrace。?
比如,?
Java代碼??
怎樣在容器級別全局地規定這個規則呢?我們不知道哪些組件需要注射Logger,也不知道這些組件在哪個參數注射Logger對象。?
2。怎樣提供缺省參數?這樣,如果某個參數的需要可以在容器中解析,則擁這個解析出來的實例,否則,使用一個缺省組件。?
在下一節,我們會通過這兩個例子來繼續解釋co的重構過程。
from:http://ajoo.iteye.com/blog/23329
總結
以上是生活随笔為你收集整理的论面向组合子程序设计方法 之 重构2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 论面向组合子程序设计方法 之 南无阿弥陀
- 下一篇: 读《程序员必读的职业规划书》