日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

论面向组合子程序设计方法 之 重构

發(fā)布時(shí)間:2025/3/21 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 论面向组合子程序设计方法 之 重构 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
迄今,發(fā)現(xiàn)典型的幾種疑問是:?
1。組合子的設(shè)計(jì)要求正交,要求最基本,這是不是太難達(dá)到呢??
2。面對(duì)一些現(xiàn)實(shí)中更復(fù)雜的需求,組合子怎樣scale up呢??

其實(shí),這兩者都指向一個(gè)答案:重構(gòu)。?


要設(shè)計(jì)一個(gè)完全正交,原子到不可再分的組合子,也許不是總是那么容易。但是,我們并不需要一開始就設(shè)計(jì)出來完美的組合子設(shè)計(jì)。?

比如,我前面的logging例子,TimestampLogger負(fù)責(zé)給在一行的開頭打印當(dāng)前時(shí)間。?
然后readonly提出了一個(gè)新的需要:打印調(diào)用這個(gè)logger的那個(gè)java文件的類名字和行號(hào)。?

分析這個(gè)需求,可以發(fā)現(xiàn),兩者都要求在一行的開始打印一些東西。似乎有些共性.?
這個(gè)"在行首打印一些前綴"就成了一個(gè)可以抽象出來的共性.于是重構(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接口用來抽象往行首打印的前綴。這個(gè)地方之所以不是一個(gè)String,是因?yàn)榭紤]到生成這個(gè)前綴可能是比較昂貴的(比如打印行號(hào),這需要?jiǎng)?chuàng)建一個(gè)臨時(shí)異常對(duì)象)?

    另外,真正的Logger接口,會(huì)負(fù)責(zé)打印所有的原始類型和Object類型,例子中我們簡化了這個(gè)接口,為了演示方便。?


    然后,先重構(gòu)timestamp:?

    Java代碼??
  • class?TimestampFactory?implements?Factory{??
  • ??private?final?DateFormat?fmt;??
  • ??public?String?create();{??
  • ????return?fmt.format(new?Date(););;??
  • ??}??
  • }??


  • 這樣,就把timestamp和“行首打印”解耦了出來。?


    下面添加TraceBackFactory,負(fù)責(zé)打印當(dāng)前行號(hào)等源代碼相關(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的實(shí)現(xiàn)我就不寫了。?

    注意,到現(xiàn)在為止,這個(gè)重構(gòu)都是經(jīng)典的oo的思路,劃分責(zé)任,按照責(zé)任定義Factory, SourceLocationFormat等等接口,依賴注入等。完全沒有co的影子。?

    這也說明,在co里面,我們不是不能采用oo,就象在oo里面,我們也可以圍繞某個(gè)接口按照co來提供一整套的實(shí)現(xiàn)一樣,就象在oo里面,我們也可以在函數(shù)內(nèi)部用po的方法來實(shí)現(xiàn)某個(gè)具體功能一樣。?


    下面開始對(duì)factory做一些co的勾當(dāng):?
    先是最簡單的:?

    Java代碼??
  • class?ReturnFactory?implements?Factory{??
  • ??private?final?String?s;??
  • ??public?String?create();{return?s;}??
  • }??


  • 然后是兩個(gè)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();;??
  • ??}??
  • }??




  • 最后,我們把這幾個(gè)零件組合在一起:?

    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);;??
  • }??


  • 如此,基本上,在行首添加?xùn)|西的需求就差不多了,我們甚至也可以在行尾添加?xùn)|西,還可以重用這些factory的組合子。?

    另一點(diǎn)我想說明的是:這種重構(gòu)是相當(dāng)局部的,僅僅影響幾個(gè)組合子,而并不影響整個(gè)組合子框架。?


    真正影響組合子框架的,是Logger接口本身的變化。假設(shè),readonly提出了一個(gè)非常好的意見:printException應(yīng)該也接受level,因?yàn)槲覀儜?yīng)該也可以選擇一個(gè)exception的重要程度。?


    那么,如果需要做這個(gè)變化,很不幸的是,所有的實(shí)現(xiàn)這個(gè)接口的類都要改變。?

    這是不是co的一個(gè)缺陷呢??


    我說不是。?
    即使是oo,如果你需要改動(dòng)接口,所有的實(shí)現(xiàn)類也都要改動(dòng)。co對(duì)這種情況,其實(shí)還是做了很大的貢獻(xiàn)來避免的:?
    只有原子組合子需要實(shí)現(xiàn)這個(gè)接口,而派生的組合子和客戶代碼,根本就不會(huì)被波及到。?
    而co相比于oo,同樣面對(duì)相同復(fù)雜的需求,往往原子組合子的數(shù)目遠(yuǎn)遠(yuǎn)小于實(shí)際上要實(shí)現(xiàn)的語義數(shù),大量的需求要求的語義,被通過組合基本粒子來實(shí)現(xiàn)。也因此會(huì)減少直接實(shí)現(xiàn)這個(gè)接口的類的數(shù)目,降低了接口變化的波及范圍。?


    那么,這個(gè)Logger接口是怎么來的呢??

    它的形成來自兩方面:?

    1。需求。通過oo的手段分配責(zé)任,最后分析出來的一個(gè)接口。這個(gè)接口不一定是最簡化的,因?yàn)樗耆峭獠啃枨篁?qū)動(dòng)的。?

    2。組合子自身接口簡單性和完備性的需要。有些時(shí)候,我們發(fā)現(xiàn),一個(gè)組合子里面如果沒有某個(gè)方法,或者某個(gè)方法如果沒有某個(gè)參數(shù),一些組合就無法成立。這很可能說明我們的接口不是完備的。(比如那個(gè)print函數(shù))。?
    此時(shí),就需要改動(dòng)接口,并且修改原子組合子的實(shí)現(xiàn)。?
    因?yàn)檫@個(gè)變化完全是基于組合需求的完備性的,所以是co方法本身帶來的問題,而不能推諉于oo設(shè)計(jì)出來的接口。?
    也因?yàn)槿绱?#xff0c;基本組合子個(gè)數(shù)的盡量精簡就是一個(gè)目標(biāo)。能夠通過基本組合子組合而成的,就可以考慮不要直接實(shí)現(xiàn)這個(gè)接口。?
    當(dāng)然,這里面仍然有個(gè)權(quán)衡:?
    通過組合出來的不如直接實(shí)現(xiàn)的直接,可理解性,甚至可調(diào)試性,性能都會(huì)有所下降。?
    而如果選擇直接實(shí)現(xiàn)接口,那么就要做好接口一旦變化,就多出一個(gè)類要改動(dòng)這個(gè)類的心理準(zhǔn)備。?

    如何抉擇,沒有一定之規(guī)。?

    而因?yàn)?和2的目標(biāo)并不完全一致,很多時(shí)候,我們還需要在1和2之間架一個(gè)adapter以避免兩個(gè)目標(biāo)的沖突。?

    比如說,實(shí)際使用中,我可能希望Logger接口提供不要求level的println函數(shù),讓它的缺省值取INFO就好了。?

    但是,這對(duì)組合子的實(shí)現(xiàn)來說卻是不利的。這時(shí),我們也許就要把這個(gè)實(shí)現(xiàn)要求的Logger接口和組合子的Logger接口分離開來。(比如把組合子單獨(dú)挪到一個(gè)package中)。?



    Logger這個(gè)例子是非常簡單的,它雖然來自于實(shí)際項(xiàng)目,但是項(xiàng)目對(duì)logging的需求并不是太多,所以一些朋友提出了一些基于實(shí)際使用的一些問題,我只能給一個(gè)怎么做的大致輪廓,手邊卻沒有可以運(yùn)行的程序。?


    那么,下面一個(gè)例子,我們來看看一個(gè)我經(jīng)過了很多思考比較完善了的ioc容器的設(shè)計(jì)。這個(gè)設(shè)計(jì)來源于yan container。?


    先說一下ioc容器的背景知識(shí)。?

    所謂ioc容器,是一種用來組裝用ioc模式(或者叫依賴注射)設(shè)計(jì)出來的類的工具。?
    一個(gè)用ioc設(shè)計(jì)出來的類,本身對(duì)ioc容器是一無所知的。使用它的時(shí)候,可以根據(jù)實(shí)際情況選擇直接new,直接調(diào)用setter等等比較直接的方法,但是,當(dāng)這樣的組件非常非常多的時(shí)候,用一個(gè)ioc容器來統(tǒng)一管理這些對(duì)象的組裝就可以被考慮。?


    拿pico作為例子,對(duì)應(yīng)這樣一個(gè)類:?

    Java代碼??
  • class?Boy{??
  • ??private?final?Girl?girl;??
  • ??public?Boy(Girl?g);{??
  • ????this.girl?=?g;??
  • ??}??
  • ...??
  • }??


  • 我們自然可以new Boy(new Girl());?

    沒什么不好的。?

    但是,如果這種需要組裝的類太多,那么這個(gè)組裝就變成一件累人的活了。?

    于是,pico container提供了一個(gè)統(tǒng)一管理組建的方法:?
    Java代碼??
  • picocontainer?container?=?new?DefaultContainer();;??
  • container.registerComponentImplementation(Boy.class);;??
  • container.registerComponentImplementation(Girl.class);;??


  • 這個(gè)代碼,很可能不是直接寫在程序里面,而是先讀取配置文件或者什么東西,然后動(dòng)態(tài)地調(diào)用這段代碼。?

    最后,使用下面的方法來取得對(duì)象:?

    Java代碼??
  • Object?obj?=?container.getComponentInstance(Boy.class);;??


  • 注意,這個(gè)container.getXXX,本身是違反ioc的設(shè)計(jì)模式的,它 主動(dòng) 地去尋找某個(gè)組件了。所以,組件本身是忌諱調(diào)用這種api的。如果你在組件級(jí)別的代碼直接依賴ioc容器的api,那么,恭喜你,你終于成功地化神奇為腐朽了。 ?


    這段代碼,實(shí)際上應(yīng)該出現(xiàn)在系統(tǒng)的最外圍的組裝程序中。?

    當(dāng)然,這是題外話。?


    那么,我們來評(píng)估一下pico先,?

    1。讓容器自動(dòng)尋找符合某個(gè)類型的組件,叫做auto-wiring。這個(gè)功能方便,但是不能scale up。一旦系統(tǒng)復(fù)雜起來,就會(huì)造成一團(tuán)亂麻,尤其是有兩個(gè)組件都符合這個(gè)要求的時(shí)候,就會(huì)出現(xiàn)二義性。所以,必須提供讓配置者或者程序員顯示指定使用哪個(gè)組件的能力。所謂manual-wire。?
    當(dāng)然,pico實(shí)際上是提供了這個(gè)能力的,它允許你使用組件key或者組件類型來顯示地給某個(gè)組件的某個(gè)參數(shù)或者某個(gè)property指定它的那個(gè)girl。?

    但是,pico的靈活性就到這里了,它要求你的這個(gè)girl必須被直接登記在這個(gè)容器中,占用一個(gè)寶貴的全局key,即使這個(gè)girl只是專門為這個(gè)body臨時(shí)制造的夏娃。?

    在java中,遇到這種情況:?

    Java代碼??
  • void?A?createA();{??
  • ??B?b?=?new?B();;??
  • ??return?new?A(b,b);;??
  • }??


  • 我們只需要把b作為一個(gè)局部變量,構(gòu)造完A,b就扔掉了。然而,pico里面這不成,b必須被登記在這個(gè)容器中。這就相當(dāng)于你必須要把b定義成一個(gè)全局變量一樣。?
    pico的對(duì)應(yīng)代碼:?

    Java代碼??
  • container.registerComponent("b"?new?CachingComponentAdapter(new?ConstructorInjectionComponentAdapter(B.class);););;??
  • container.registerComponent("a",?new?ConstructorInjectionComponentAdapter(A.class););;??


  • 這里,為了對(duì)應(yīng)上面java代碼中的兩個(gè)參數(shù)公用一個(gè)b的實(shí)例的要求,必須把a(bǔ)登記成一個(gè)singleton。CachingComponentAdapter負(fù)責(zé)singleton化某個(gè)組件,而ConstructorInjectionComponentAdapter就是一個(gè)調(diào)用構(gòu)造函數(shù)的組建匹配器。?


    當(dāng)然,這樣做其實(shí)還是有麻煩的,當(dāng)container不把a(bǔ)登記成singleton的時(shí)候(pico缺省都登記成singleton,但是你可以換缺省不用singleton的container。),麻煩就來了。?

    大家可以看到,上面的createA()函數(shù)如果調(diào)用兩次,會(huì)創(chuàng)建兩個(gè)A對(duì)象,兩個(gè)B對(duì)象,而用這段pico代碼,調(diào)用兩次getComponentInstance("a"),會(huì)生成兩個(gè)A對(duì)象,但是卻只有一個(gè)B對(duì)象!因?yàn)閎被被迫登記為singleton了。?





    2。pico除了支持constructor injection,也支持setter injection甚至factory method injection。(對(duì)最后一點(diǎn)我有點(diǎn)含糊,不過就假設(shè)它支持)。所以,跟spring對(duì)比,除了沒有一個(gè)配置文件,life-cycle不太優(yōu)雅之外,什么都有了。?

    但是,這就夠了嗎?如果我們把上面的那個(gè)createA函數(shù)稍微變一下:?

    Java代碼??
  • A?createA();{??
  • ??B?b?=?new?B();;??
  • ??return?new?A(b,?b.createC(x_component););;??
  • }??



  • 現(xiàn)在,我們要在b組件上面調(diào)用createC()來生成一個(gè)C對(duì)象。完了,我們要的既不是構(gòu)造函數(shù),也不是工廠方法,而是在某個(gè)臨時(shí)組件的基礎(chǔ)上調(diào)用一個(gè)函數(shù)。?

    缺省提供的幾個(gè)ComponentAdapter這時(shí)就不夠用了,我們被告知要自己實(shí)現(xiàn)ComponentAdapter。?

    實(shí)際上,pico對(duì)很多靈活性的要求的回答都是:自己實(shí)現(xiàn)ComponentAdapter。?


    這是可行的。沒什么是ComponentAdapter干不了的,如果不計(jì)工作量的話。?

    一個(gè)麻煩是:我們要直接調(diào)用pico的api來自己解析依賴了。我們要自己知道是調(diào)用container.getComponentInstance("x_component")還是container.getComponentInstance(X.class)。?
    第二個(gè)麻煩是:降低了代碼重用。自己實(shí)現(xiàn)ComponentAdapter就得自己老老實(shí)實(shí)地寫,如果自己的component adapter也要?jiǎng)討B(tài)設(shè)置java bean setter的話,甭想直接用SetterInjectionComponentAdapter,好好看java bean的api吧。?


    其實(shí),我們可以看出,pico的各種ComponentAdapter正是正宗的decorator pattern。什么CachingComponentAdapter,什么SynchronizedComponentAdapter,都是decorator。?

    但是,這也就是decorator而已了。因?yàn)闆]有圍繞組合子的思路開展設(shè)計(jì),這些decorator顯得非常隨意,沒有什么章法,沒辦法支撐起整個(gè)的ComponentAdapter的架構(gòu)。?

    下一章,我們會(huì)介紹yan container對(duì)上面提出的問題以及很多其他問題的解決方法。?

    yan container的口號(hào)是:只要你直接組裝能夠做到的,容器就能做到。?
    不管你是不是用構(gòu)造函數(shù),靜態(tài)方法,java bean,構(gòu)造函數(shù)然后再調(diào)用某個(gè)方法,等等等等。?
    而且yan container的目標(biāo)是,你幾乎不用自己實(shí)現(xiàn)component adapter,所有的需求,都通過組合各種已經(jīng)存在的組合子來完成。?


    對(duì)我們前面那個(gè)很不厚道地用來刁難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é),你會(huì)發(fā)現(xiàn),用面向組合子的方法,ioc容器這種東西真的不難。我們不需要仔細(xì)分析各種需求,精心分配責(zé)任。讓我們?cè)俅误w驗(yàn)一下吊兒郎當(dāng)不知不覺間就天下大治的感覺吧。?

    待續(xù)。

    from:http://ajoo.iteye.com/blog/23314

    總結(jié)

    以上是生活随笔為你收集整理的论面向组合子程序设计方法 之 重构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。