论面向组合子程序设计方法 之 重构
生活随笔
收集整理的這篇文章主要介紹了
论面向组合子程序设计方法 之 重构
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
迄今,發(fā)現(xiàn)典型的幾種疑問是:?
1。組合子的設(shè)計要求正交,要求最基本,這是不是太難達到呢??
2。面對一些現(xiàn)實中更復雜的需求,組合子怎樣scale up呢??
其實,這兩者都指向一個答案:重構(gòu)。?
要設(shè)計一個完全正交,原子到不可再分的組合子,也許不是總是那么容易。但是,我們并不需要一開始就設(shè)計出來完美的組合子設(shè)計。?
比如,我前面的logging例子,TimestampLogger負責給在一行的開頭打印當前時間。?
然后readonly提出了一個新的需要:打印調(diào)用這個logger的那個java文件的類名字和行號。?
分析這個需求,可以發(fā)現(xiàn),兩者都要求在一行的開始打印一些東西。似乎有些共性.?
這個"在行首打印一些前綴"就成了一個可以抽象出來的共性.于是重構(gòu):?
Java代碼??interface?Factory{?? ??String?create();;?? }?? class?PrefixLogger?implements?Logger{?? ??private?final?Logger?logger;?? ??private?final?Factory?factory;?? ??private?boolean?freshline?=?true;?? ?? ??private?void?prefix(int?lvl);{?? ????if(freshline);{?? ??????Object?r?=?factory.create();;?? ??????if(r!=null);?? ????????logger.print(lvl,?r);;?? ??????freshline?=?false;?? ????}?? ??}?? ??public?void?print(int?lvl,?String?s);{?? ????prefix(lvl);;?? ????logger.print(lvl,?s);;?? ??}?? ??public?void?println(int?lvl,?String?s);{?? ????prefix(lvl);;?? ????logger.println(lvl,?s);;?? ????freshline?=?true;?? ??}?? ??public?void?printException(int?lvl,?Throwable?e);{?? ????prefix(lvl);;?? ????logger.printException(lvl,?e);;?? ????freshline?=?true;?? ??}?? }??
這里,Factory接口用來抽象往行首打印的前綴。這個地方之所以不是一個String,是因為考慮到生成這個前綴可能是比較昂貴的(比如打印行號,這需要創(chuàng)建一個臨時異常對象)?
另外,真正的Logger接口,會負責打印所有的原始類型和Object類型,例子中我們簡化了這個接口,為了演示方便。?
然后,先重構(gòu)timestamp:?
Java代碼??class?TimestampFactory?implements?Factory{?? ??private?final?DateFormat?fmt;?? ??public?String?create();{?? ????return?fmt.format(new?Date(););;?? ??}?? }??
這樣,就把timestamp和“行首打印”解耦了出來。?
下面添加TraceBackFactory,負責打印當前行號等源代碼相關(guān)信息。?
Java代碼??interface?SourceLocationFormat{?? ??String?format(StackTraceElement?frame);;?? }?? class?TraceBackFactory?implements?Factory{?? ??private?final?SourceLocationFormat?fmt;?? ??public?String?create();{?? ????final?StackTraceElement?frame?=?getNearestUserFrame();;?? ????if(frame!=null);?? ??????return?fmt.format(frame);;?? ????else?return?null;?? ??}?? ??private?StackTraceElement?getNearestUserFrame();{?? ????final?StackTraceElement[]?frames?=?new?Throwable();.getStackTrace();;?? ????foreach(frame:?frames);{?? ??????if(!frame.getClassName();.startsWith("org.mylogging"););{?? ????????//user?frame?? ????????return?frame;?? ??????}?? ????}?? ????return?null;?? ??}?? }??
具體的SourceLocationFormat的實現(xiàn)我就不寫了。?
注意,到現(xiàn)在為止,這個重構(gòu)都是經(jīng)典的oo的思路,劃分責任,按照責任定義Factory, SourceLocationFormat等等接口,依賴注入等。完全沒有co的影子。?
這也說明,在co里面,我們不是不能采用oo,就象在oo里面,我們也可以圍繞某個接口按照co來提供一整套的實現(xiàn)一樣,就象在oo里面,我們也可以在函數(shù)內(nèi)部用po的方法來實現(xiàn)某個具體功能一樣。?
下面開始對factory做一些co的勾當:?
先是最簡單的:?
Java代碼??class?ReturnFactory?implements?Factory{?? ??private?final?String?s;?? ??public?String?create();{return?s;}?? }??
然后是兩個factory的串聯(lián),?
Java代碼??class?ConcatFactory?implements?Factory{?? ??private?final?Factory[]?fs;?? ??public?String?create();{?? ????StringBuffer?buf?=?new?StringBuffer();;?? ????foreach(f:?fs);{?? ??????buf.append(f.create(););;?? ????}?? ????return?buf.toString();;?? ??}?? }??
最后,我們把這幾個零件組合在一起:?
Java代碼??Logger?myprefix(Logger?l);{?? ??Factory?timestamp?=?new?TimestampFactory(some_date_format);;?? ??Factory?traceback?=?new?TraceBackFactory(some_location_format);;?? ??Factory?both?=?new?ConcatFactory(?? ????timestamp,??? ????new?ReturnFactory("?-?");,??? ????traceback,?? ????new?ReturnFactory("?:?");?? ??);;?? ??return?new?PrefixLogger(both,?l);;?? }??
如此,基本上,在行首添加東西的需求就差不多了,我們甚至也可以在行尾添加東西,還可以重用這些factory的組合子。?
另一點我想說明的是:這種重構(gòu)是相當局部的,僅僅影響幾個組合子,而并不影響整個組合子框架。?
真正影響組合子框架的,是Logger接口本身的變化。假設(shè),readonly提出了一個非常好的意見:printException應(yīng)該也接受level,因為我們應(yīng)該也可以選擇一個exception的重要程度。?
那么,如果需要做這個變化,很不幸的是,所有的實現(xiàn)這個接口的類都要改變。?
這是不是co的一個缺陷呢??
我說不是。?
即使是oo,如果你需要改動接口,所有的實現(xiàn)類也都要改動。co對這種情況,其實還是做了很大的貢獻來避免的:?
只有原子組合子需要實現(xiàn)這個接口,而派生的組合子和客戶代碼,根本就不會被波及到。?
而co相比于oo,同樣面對相同復雜的需求,往往原子組合子的數(shù)目遠遠小于實際上要實現(xiàn)的語義數(shù),大量的需求要求的語義,被通過組合基本粒子來實現(xiàn)。也因此會減少直接實現(xiàn)這個接口的類的數(shù)目,降低了接口變化的波及范圍。?
那么,這個Logger接口是怎么來的呢??
它的形成來自兩方面:?
1。需求。通過oo的手段分配責任,最后分析出來的一個接口。這個接口不一定是最簡化的,因為它完全是外部需求驅(qū)動的。?
2。組合子自身接口簡單性和完備性的需要。有些時候,我們發(fā)現(xiàn),一個組合子里面如果沒有某個方法,或者某個方法如果沒有某個參數(shù),一些組合就無法成立。這很可能說明我們的接口不是完備的。(比如那個print函數(shù))。?
此時,就需要改動接口,并且修改原子組合子的實現(xiàn)。?
因為這個變化完全是基于組合需求的完備性的,所以是co方法本身帶來的問題,而不能推諉于oo設(shè)計出來的接口。?
也因為如此,基本組合子個數(shù)的盡量精簡就是一個目標。能夠通過基本組合子組合而成的,就可以考慮不要直接實現(xiàn)這個接口。?
當然,這里面仍然有個權(quán)衡:?
通過組合出來的不如直接實現(xiàn)的直接,可理解性,甚至可調(diào)試性,性能都會有所下降。?
而如果選擇直接實現(xiàn)接口,那么就要做好接口一旦變化,就多出一個類要改動這個類的心理準備。?
如何抉擇,沒有一定之規(guī)。?
而因為1和2的目標并不完全一致,很多時候,我們還需要在1和2之間架一個adapter以避免兩個目標的沖突。?
比如說,實際使用中,我可能希望Logger接口提供不要求level的println函數(shù),讓它的缺省值取INFO就好了。?
但是,這對組合子的實現(xiàn)來說卻是不利的。這時,我們也許就要把這個實現(xiàn)要求的Logger接口和組合子的Logger接口分離開來。(比如把組合子單獨挪到一個package中)。?
Logger這個例子是非常簡單的,它雖然來自于實際項目,但是項目對logging的需求并不是太多,所以一些朋友提出了一些基于實際使用的一些問題,我只能給一個怎么做的大致輪廓,手邊卻沒有可以運行的程序。?
那么,下面一個例子,我們來看看一個我經(jīng)過了很多思考比較完善了的ioc容器的設(shè)計。這個設(shè)計來源于yan container。?
先說一下ioc容器的背景知識。?
所謂ioc容器,是一種用來組裝用ioc模式(或者叫依賴注射)設(shè)計出來的類的工具。?
一個用ioc設(shè)計出來的類,本身對ioc容器是一無所知的。使用它的時候,可以根據(jù)實際情況選擇直接new,直接調(diào)用setter等等比較直接的方法,但是,當這樣的組件非常非常多的時候,用一個ioc容器來統(tǒng)一管理這些對象的組裝就可以被考慮。?
拿pico作為例子,對應(yīng)這樣一個類:?
Java代碼??class?Boy{?? ??private?final?Girl?girl;?? ??public?Boy(Girl?g);{?? ????this.girl?=?g;?? ??}?? ...?? }??
我們自然可以new Boy(new Girl());?
沒什么不好的。?
但是,如果這種需要組裝的類太多,那么這個組裝就變成一件累人的活了。?
于是,pico container提供了一個統(tǒng)一管理組建的方法:?
Java代碼??picocontainer?container?=?new?DefaultContainer();;?? container.registerComponentImplementation(Boy.class);;?? container.registerComponentImplementation(Girl.class);;??
這個代碼,很可能不是直接寫在程序里面,而是先讀取配置文件或者什么東西,然后動態(tài)地調(diào)用這段代碼。?
最后,使用下面的方法來取得對象:?
Java代碼??Object?obj?=?container.getComponentInstance(Boy.class);;??
注意,這個container.getXXX,本身是違反ioc的設(shè)計模式的,它 主動 地去尋找某個組件了。所以,組件本身是忌諱調(diào)用這種api的。如果你在組件級別的代碼直接依賴ioc容器的api,那么,恭喜你,你終于成功地化神奇為腐朽了。 ?
這段代碼,實際上應(yīng)該出現(xiàn)在系統(tǒng)的最外圍的組裝程序中。?
當然,這是題外話。?
那么,我們來評估一下pico先,?
1。讓容器自動尋找符合某個類型的組件,叫做auto-wiring。這個功能方便,但是不能scale up。一旦系統(tǒng)復雜起來,就會造成一團亂麻,尤其是有兩個組件都符合這個要求的時候,就會出現(xiàn)二義性。所以,必須提供讓配置者或者程序員顯示指定使用哪個組件的能力。所謂manual-wire。?
當然,pico實際上是提供了這個能力的,它允許你使用組件key或者組件類型來顯示地給某個組件的某個參數(shù)或者某個property指定它的那個girl。?
但是,pico的靈活性就到這里了,它要求你的這個girl必須被直接登記在這個容器中,占用一個寶貴的全局key,即使這個girl只是專門為這個body臨時制造的夏娃。?
在java中,遇到這種情況:?
Java代碼??void?A?createA();{?? ??B?b?=?new?B();;?? ??return?new?A(b,b);;?? }??
我們只需要把b作為一個局部變量,構(gòu)造完A,b就扔掉了。然而,pico里面這不成,b必須被登記在這個容器中。這就相當于你必須要把b定義成一個全局變量一樣。?
pico的對應(yīng)代碼:?
Java代碼??container.registerComponent("b"?new?CachingComponentAdapter(new?ConstructorInjectionComponentAdapter(B.class);););;?? container.registerComponent("a",?new?ConstructorInjectionComponentAdapter(A.class););;??
這里,為了對應(yīng)上面java代碼中的兩個參數(shù)公用一個b的實例的要求,必須把a登記成一個singleton。CachingComponentAdapter負責singleton化某個組件,而ConstructorInjectionComponentAdapter就是一個調(diào)用構(gòu)造函數(shù)的組建匹配器。?
當然,這樣做其實還是有麻煩的,當container不把a登記成singleton的時候(pico缺省都登記成singleton,但是你可以換缺省不用singleton的container。),麻煩就來了。?
大家可以看到,上面的createA()函數(shù)如果調(diào)用兩次,會創(chuàng)建兩個A對象,兩個B對象,而用這段pico代碼,調(diào)用兩次getComponentInstance("a"),會生成兩個A對象,但是卻只有一個B對象!因為b被被迫登記為singleton了。?
2。pico除了支持constructor injection,也支持setter injection甚至factory method injection。(對最后一點我有點含糊,不過就假設(shè)它支持)。所以,跟spring對比,除了沒有一個配置文件,life-cycle不太優(yōu)雅之外,什么都有了。?
但是,這就夠了嗎?如果我們把上面的那個createA函數(shù)稍微變一下:?
Java代碼??A?createA();{?? ??B?b?=?new?B();;?? ??return?new?A(b,?b.createC(x_component););;?? }??
現(xiàn)在,我們要在b組件上面調(diào)用createC()來生成一個C對象。完了,我們要的既不是構(gòu)造函數(shù),也不是工廠方法,而是在某個臨時組件的基礎(chǔ)上調(diào)用一個函數(shù)。?
缺省提供的幾個ComponentAdapter這時就不夠用了,我們被告知要自己實現(xiàn)ComponentAdapter。?
實際上,pico對很多靈活性的要求的回答都是:自己實現(xiàn)ComponentAdapter。?
這是可行的。沒什么是ComponentAdapter干不了的,如果不計工作量的話。?
一個麻煩是:我們要直接調(diào)用pico的api來自己解析依賴了。我們要自己知道是調(diào)用container.getComponentInstance("x_component")還是container.getComponentInstance(X.class)。?
第二個麻煩是:降低了代碼重用。自己實現(xiàn)ComponentAdapter就得自己老老實實地寫,如果自己的component adapter也要動態(tài)設(shè)置java bean setter的話,甭想直接用SetterInjectionComponentAdapter,好好看java bean的api吧。?
其實,我們可以看出,pico的各種ComponentAdapter正是正宗的decorator pattern。什么CachingComponentAdapter,什么SynchronizedComponentAdapter,都是decorator。?
但是,這也就是decorator而已了。因為沒有圍繞組合子的思路開展設(shè)計,這些decorator顯得非常隨意,沒有什么章法,沒辦法支撐起整個的ComponentAdapter的架構(gòu)。?
下一章,我們會介紹yan container對上面提出的問題以及很多其他問題的解決方法。?
yan container的口號是:只要你直接組裝能夠做到的,容器就能做到。?
不管你是不是用構(gòu)造函數(shù),靜態(tài)方法,java bean,構(gòu)造函數(shù)然后再調(diào)用某個方法,等等等等。?
而且yan container的目標是,你幾乎不用自己實現(xiàn)component adapter,所有的需求,都通過組合各種已經(jīng)存在的組合子來完成。?
對我們前面那個很不厚道地用來刁難pico的例子,yan的解決方法是:?
Java代碼??b_component?=?Components.ctor(B.class);.singleton();;?? a_component?=?Components.ctor(A.class);?? ??.withArgument(0,?b_component);?? ??.withArgument(1,?b_component.method("createC"););;??
b_component不需要登記在容器中,它作為局部component存在。?
是不是非常declarative呢??
下一節(jié),你會發(fā)現(xiàn),用面向組合子的方法,ioc容器這種東西真的不難。我們不需要仔細分析各種需求,精心分配責任。讓我們再次體驗一下吊兒郎當不知不覺間就天下大治的感覺吧。?
1。組合子的設(shè)計要求正交,要求最基本,這是不是太難達到呢??
2。面對一些現(xiàn)實中更復雜的需求,組合子怎樣scale up呢??
其實,這兩者都指向一個答案:重構(gòu)。?
要設(shè)計一個完全正交,原子到不可再分的組合子,也許不是總是那么容易。但是,我們并不需要一開始就設(shè)計出來完美的組合子設(shè)計。?
比如,我前面的logging例子,TimestampLogger負責給在一行的開頭打印當前時間。?
然后readonly提出了一個新的需要:打印調(diào)用這個logger的那個java文件的類名字和行號。?
分析這個需求,可以發(fā)現(xiàn),兩者都要求在一行的開始打印一些東西。似乎有些共性.?
這個"在行首打印一些前綴"就成了一個可以抽象出來的共性.于是重構(gòu):?
Java代碼??
這里,Factory接口用來抽象往行首打印的前綴。這個地方之所以不是一個String,是因為考慮到生成這個前綴可能是比較昂貴的(比如打印行號,這需要創(chuàng)建一個臨時異常對象)?
另外,真正的Logger接口,會負責打印所有的原始類型和Object類型,例子中我們簡化了這個接口,為了演示方便。?
然后,先重構(gòu)timestamp:?
Java代碼??
這樣,就把timestamp和“行首打印”解耦了出來。?
下面添加TraceBackFactory,負責打印當前行號等源代碼相關(guān)信息。?
Java代碼??
具體的SourceLocationFormat的實現(xiàn)我就不寫了。?
注意,到現(xiàn)在為止,這個重構(gòu)都是經(jīng)典的oo的思路,劃分責任,按照責任定義Factory, SourceLocationFormat等等接口,依賴注入等。完全沒有co的影子。?
這也說明,在co里面,我們不是不能采用oo,就象在oo里面,我們也可以圍繞某個接口按照co來提供一整套的實現(xiàn)一樣,就象在oo里面,我們也可以在函數(shù)內(nèi)部用po的方法來實現(xiàn)某個具體功能一樣。?
下面開始對factory做一些co的勾當:?
先是最簡單的:?
Java代碼??
然后是兩個factory的串聯(lián),?
Java代碼??
最后,我們把這幾個零件組合在一起:?
Java代碼??
如此,基本上,在行首添加東西的需求就差不多了,我們甚至也可以在行尾添加東西,還可以重用這些factory的組合子。?
另一點我想說明的是:這種重構(gòu)是相當局部的,僅僅影響幾個組合子,而并不影響整個組合子框架。?
真正影響組合子框架的,是Logger接口本身的變化。假設(shè),readonly提出了一個非常好的意見:printException應(yīng)該也接受level,因為我們應(yīng)該也可以選擇一個exception的重要程度。?
那么,如果需要做這個變化,很不幸的是,所有的實現(xiàn)這個接口的類都要改變。?
這是不是co的一個缺陷呢??
我說不是。?
即使是oo,如果你需要改動接口,所有的實現(xiàn)類也都要改動。co對這種情況,其實還是做了很大的貢獻來避免的:?
只有原子組合子需要實現(xiàn)這個接口,而派生的組合子和客戶代碼,根本就不會被波及到。?
而co相比于oo,同樣面對相同復雜的需求,往往原子組合子的數(shù)目遠遠小于實際上要實現(xiàn)的語義數(shù),大量的需求要求的語義,被通過組合基本粒子來實現(xiàn)。也因此會減少直接實現(xiàn)這個接口的類的數(shù)目,降低了接口變化的波及范圍。?
那么,這個Logger接口是怎么來的呢??
它的形成來自兩方面:?
1。需求。通過oo的手段分配責任,最后分析出來的一個接口。這個接口不一定是最簡化的,因為它完全是外部需求驅(qū)動的。?
2。組合子自身接口簡單性和完備性的需要。有些時候,我們發(fā)現(xiàn),一個組合子里面如果沒有某個方法,或者某個方法如果沒有某個參數(shù),一些組合就無法成立。這很可能說明我們的接口不是完備的。(比如那個print函數(shù))。?
此時,就需要改動接口,并且修改原子組合子的實現(xiàn)。?
因為這個變化完全是基于組合需求的完備性的,所以是co方法本身帶來的問題,而不能推諉于oo設(shè)計出來的接口。?
也因為如此,基本組合子個數(shù)的盡量精簡就是一個目標。能夠通過基本組合子組合而成的,就可以考慮不要直接實現(xiàn)這個接口。?
當然,這里面仍然有個權(quán)衡:?
通過組合出來的不如直接實現(xiàn)的直接,可理解性,甚至可調(diào)試性,性能都會有所下降。?
而如果選擇直接實現(xiàn)接口,那么就要做好接口一旦變化,就多出一個類要改動這個類的心理準備。?
如何抉擇,沒有一定之規(guī)。?
而因為1和2的目標并不完全一致,很多時候,我們還需要在1和2之間架一個adapter以避免兩個目標的沖突。?
比如說,實際使用中,我可能希望Logger接口提供不要求level的println函數(shù),讓它的缺省值取INFO就好了。?
但是,這對組合子的實現(xiàn)來說卻是不利的。這時,我們也許就要把這個實現(xiàn)要求的Logger接口和組合子的Logger接口分離開來。(比如把組合子單獨挪到一個package中)。?
Logger這個例子是非常簡單的,它雖然來自于實際項目,但是項目對logging的需求并不是太多,所以一些朋友提出了一些基于實際使用的一些問題,我只能給一個怎么做的大致輪廓,手邊卻沒有可以運行的程序。?
那么,下面一個例子,我們來看看一個我經(jīng)過了很多思考比較完善了的ioc容器的設(shè)計。這個設(shè)計來源于yan container。?
先說一下ioc容器的背景知識。?
所謂ioc容器,是一種用來組裝用ioc模式(或者叫依賴注射)設(shè)計出來的類的工具。?
一個用ioc設(shè)計出來的類,本身對ioc容器是一無所知的。使用它的時候,可以根據(jù)實際情況選擇直接new,直接調(diào)用setter等等比較直接的方法,但是,當這樣的組件非常非常多的時候,用一個ioc容器來統(tǒng)一管理這些對象的組裝就可以被考慮。?
拿pico作為例子,對應(yīng)這樣一個類:?
Java代碼??
我們自然可以new Boy(new Girl());?
沒什么不好的。?
但是,如果這種需要組裝的類太多,那么這個組裝就變成一件累人的活了。?
于是,pico container提供了一個統(tǒng)一管理組建的方法:?
Java代碼??
這個代碼,很可能不是直接寫在程序里面,而是先讀取配置文件或者什么東西,然后動態(tài)地調(diào)用這段代碼。?
最后,使用下面的方法來取得對象:?
Java代碼??
注意,這個container.getXXX,本身是違反ioc的設(shè)計模式的,它 主動 地去尋找某個組件了。所以,組件本身是忌諱調(diào)用這種api的。如果你在組件級別的代碼直接依賴ioc容器的api,那么,恭喜你,你終于成功地化神奇為腐朽了。 ?
這段代碼,實際上應(yīng)該出現(xiàn)在系統(tǒng)的最外圍的組裝程序中。?
當然,這是題外話。?
那么,我們來評估一下pico先,?
1。讓容器自動尋找符合某個類型的組件,叫做auto-wiring。這個功能方便,但是不能scale up。一旦系統(tǒng)復雜起來,就會造成一團亂麻,尤其是有兩個組件都符合這個要求的時候,就會出現(xiàn)二義性。所以,必須提供讓配置者或者程序員顯示指定使用哪個組件的能力。所謂manual-wire。?
當然,pico實際上是提供了這個能力的,它允許你使用組件key或者組件類型來顯示地給某個組件的某個參數(shù)或者某個property指定它的那個girl。?
但是,pico的靈活性就到這里了,它要求你的這個girl必須被直接登記在這個容器中,占用一個寶貴的全局key,即使這個girl只是專門為這個body臨時制造的夏娃。?
在java中,遇到這種情況:?
Java代碼??
我們只需要把b作為一個局部變量,構(gòu)造完A,b就扔掉了。然而,pico里面這不成,b必須被登記在這個容器中。這就相當于你必須要把b定義成一個全局變量一樣。?
pico的對應(yīng)代碼:?
Java代碼??
這里,為了對應(yīng)上面java代碼中的兩個參數(shù)公用一個b的實例的要求,必須把a登記成一個singleton。CachingComponentAdapter負責singleton化某個組件,而ConstructorInjectionComponentAdapter就是一個調(diào)用構(gòu)造函數(shù)的組建匹配器。?
當然,這樣做其實還是有麻煩的,當container不把a登記成singleton的時候(pico缺省都登記成singleton,但是你可以換缺省不用singleton的container。),麻煩就來了。?
大家可以看到,上面的createA()函數(shù)如果調(diào)用兩次,會創(chuàng)建兩個A對象,兩個B對象,而用這段pico代碼,調(diào)用兩次getComponentInstance("a"),會生成兩個A對象,但是卻只有一個B對象!因為b被被迫登記為singleton了。?
2。pico除了支持constructor injection,也支持setter injection甚至factory method injection。(對最后一點我有點含糊,不過就假設(shè)它支持)。所以,跟spring對比,除了沒有一個配置文件,life-cycle不太優(yōu)雅之外,什么都有了。?
但是,這就夠了嗎?如果我們把上面的那個createA函數(shù)稍微變一下:?
Java代碼??
現(xiàn)在,我們要在b組件上面調(diào)用createC()來生成一個C對象。完了,我們要的既不是構(gòu)造函數(shù),也不是工廠方法,而是在某個臨時組件的基礎(chǔ)上調(diào)用一個函數(shù)。?
缺省提供的幾個ComponentAdapter這時就不夠用了,我們被告知要自己實現(xiàn)ComponentAdapter。?
實際上,pico對很多靈活性的要求的回答都是:自己實現(xiàn)ComponentAdapter。?
這是可行的。沒什么是ComponentAdapter干不了的,如果不計工作量的話。?
一個麻煩是:我們要直接調(diào)用pico的api來自己解析依賴了。我們要自己知道是調(diào)用container.getComponentInstance("x_component")還是container.getComponentInstance(X.class)。?
第二個麻煩是:降低了代碼重用。自己實現(xiàn)ComponentAdapter就得自己老老實實地寫,如果自己的component adapter也要動態(tài)設(shè)置java bean setter的話,甭想直接用SetterInjectionComponentAdapter,好好看java bean的api吧。?
其實,我們可以看出,pico的各種ComponentAdapter正是正宗的decorator pattern。什么CachingComponentAdapter,什么SynchronizedComponentAdapter,都是decorator。?
但是,這也就是decorator而已了。因為沒有圍繞組合子的思路開展設(shè)計,這些decorator顯得非常隨意,沒有什么章法,沒辦法支撐起整個的ComponentAdapter的架構(gòu)。?
下一章,我們會介紹yan container對上面提出的問題以及很多其他問題的解決方法。?
yan container的口號是:只要你直接組裝能夠做到的,容器就能做到。?
不管你是不是用構(gòu)造函數(shù),靜態(tài)方法,java bean,構(gòu)造函數(shù)然后再調(diào)用某個方法,等等等等。?
而且yan container的目標是,你幾乎不用自己實現(xiàn)component adapter,所有的需求,都通過組合各種已經(jīng)存在的組合子來完成。?
對我們前面那個很不厚道地用來刁難pico的例子,yan的解決方法是:?
Java代碼??
b_component不需要登記在容器中,它作為局部component存在。?
是不是非常declarative呢??
下一節(jié),你會發(fā)現(xiàn),用面向組合子的方法,ioc容器這種東西真的不難。我們不需要仔細分析各種需求,精心分配責任。讓我們再次體驗一下吊兒郎當不知不覺間就天下大治的感覺吧。?
待續(xù)。
from:http://ajoo.iteye.com/blog/23314
總結(jié)
以上是生活随笔為你收集整理的论面向组合子程序设计方法 之 重构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 论面向组合子程序设计方法 之 oracl
- 下一篇: 论面向组合子程序设计方法 之 南无阿弥陀