论面向组合子程序设计方法 之 燃烧的荆棘
生活随笔
收集整理的這篇文章主要介紹了
论面向组合子程序设计方法 之 燃烧的荆棘
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
唧唧歪歪一大堆。肯定早有人不耐煩了。?
"你丫還有沒有點(diǎn)實(shí)在的東西呀?"?
要是我,可能也早就忍不住了。?
好,好。我其實(shí)并沒有忘記前面說的那個(gè)logging的例子。賣了這么長時(shí)間的關(guān)子,除了有想形而上的虛榮心外,也是想給大家多一點(diǎn)時(shí)間來嚼一下這個(gè)例子,讓熟悉OO的朋友肚子里面多少有個(gè)腹稿。?
下面,我來繼續(xù)上回書說到的這個(gè)logging。?
前面列舉了那么一大堆亂七八糟的需求,不知道是不是有人和我一樣看著這些繁雜的條目鬧心。我在做的時(shí)候其實(shí)只想了五六條需求,就已經(jīng)開始煩了。何況還有一些暫時(shí)不知道如何抉擇的幾個(gè)疑問點(diǎn)。最初Kiss出來的那個(gè)logger實(shí)現(xiàn)明顯不能用了。refactor的價(jià)值也有限。?
怎么辦?智慧果也許給了我們智慧,但是這智慧畢竟是有限的。側(cè)頭看去,驀然看見荊棘里跳動(dòng)的火焰,那是上帝的光芒么??
好,既然這條路看來比較坎坷,換條路試試看也未嘗不可。走這條路的好處在于,我們可以暫時(shí)不用理會(huì)這些討厭的“需求”了。CO這個(gè)方法論不是以需求為中心,而是從底層的組合子出發(fā),就像小孩子擺弄積木,我們只管發(fā)揮自己的想象力,看看我們能擺弄出些什么東西來就好了。?
如果一切順利,我們希望能夠跨越瀚海高山,走到流著奶和蜜的土地。所有這些亂七八糟的需求,我們希望他們能夠被簡單地配置,或者至少能夠通過聲明式的方法來“聲明”,而不是通過過程式的方法來“實(shí)現(xiàn)”。?
出發(fā)之前,鼓舞以下士氣先。?
前面charon說,這種東西類似公理系統(tǒng)。確實(shí),非常非常類似。不過,門檻并不像這個(gè)名字聽來那么嚇人的高。我們并不需要一下子就完全把完備性考慮清楚,我們不是上帝,沒有這么大本事。?
類似于xp + refactoring,我們的組合子的建造也一樣可以摸著石頭過河,走一步看一步的。?
比如,假如我的Logger接口定義的時(shí)候沒有print函數(shù)而只有println,那么后面我們會(huì)發(fā)現(xiàn)在制造打印當(dāng)前時(shí)間的組合子的時(shí)候會(huì)出現(xiàn)麻煩。不過,沒關(guān)系,等到意識(shí)到我們需要一個(gè)print()函數(shù)的時(shí)候,回過頭來refactor也是可以的。?
為了說明這點(diǎn),我們現(xiàn)在假設(shè)Logger接口是這樣:?
Java代碼??interface?Logger{?? ??println(int?level,?String?msg);;?? ??printException(Throwable?e);;?? }??
好,路漫漫而修遠(yuǎn)兮,我們就這樣按照上帝的昭示上路吧,雖然前路云封霧鎖。?
首先,任何組合子系統(tǒng)的建立,都起步于最最基本最最簡單的組合子。然后再一步步組合。?
比如,布爾代數(shù)必然起步于true和false;自然數(shù)起步于0,1,然后就可以推演出整個(gè)系統(tǒng)。?
那么,對Logger來說,有0和1嗎??
有的。?
0就是一個(gè)什么都不做的Logger。?
Java代碼??class?NopLogger?implements?Logger{?? ??public?void?println(int?level,?String?msg);{}?? ??public?void?printException(Throwable?e);{}?? }??
(注,不要小看這個(gè)什么都不做的Logger,它的作用大著呢。你能想象一個(gè)沒有0的整數(shù)系統(tǒng)嗎?)?
什么是1呢?一個(gè)直接向文件中打印信息的應(yīng)該就是一個(gè)1了。?
偽碼如下:?
Java代碼??class?FileLogger?implements?Logger{?? ??public?void?println(int?level,?String?msg);{?? ????write?msg?to?log?file.?? ??}?? ??public?void?printException(Throwable?e);{?? ????write?exceptioon?to?log?file.?? ??}?? }??
那么,怎么實(shí)現(xiàn)這個(gè)類呢??
這里,我們遇到一個(gè)東西要抉擇,文件打開是要關(guān)閉的。那么誰負(fù)責(zé)打開文件?誰負(fù)責(zé)關(guān)閉?是否要在構(gòu)造函數(shù)中new FileOutputStream()?,然后再提供一個(gè)close()函數(shù)來close這個(gè)stream??
答案是:不要。?
無論是ioc也好,還是CO的組合方法也好,一個(gè)原則就是要盡量保持事情簡單。那么,什么最簡單?我們此處真正需要的是什么?一個(gè)file么??
不,其實(shí)一個(gè)PrintWriter就足夠了。?
當(dāng)然,最終文件必須還是要通過代碼打開,關(guān)閉。但是,既然反正也要打開文件,我們這里不去管也不會(huì)增加什么工作量,對么?那么為什么不樂得清閑呢??
“先天下之憂而憂”這種思想是最最要不得的。?
于是,最簡單的1應(yīng)該是這樣:?
Java代碼??class?WriterLogger?implements?Logger{?? ??private?final?PrintWriter?writer;?? ??public?void?println(int?level,?String?msg);{?? ????writer.println(msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????e.printStackTrace(writer);;?? ??}?? ??WriterLogger(PrintWriter?writer);{?? ?????this.writer?=?writer;?? ??}?? }??
“哈!還說什么CO。你這不是剽竊ioc pattern嗎?”?
完了,被抓個(gè)現(xiàn)形。還真是ioc pattern。其實(shí),世界上本來沒有什么新東西,所謂“方法論”,本來是一種思考方法,而不是說形式上必然和別人不同。不過,說我剽竊,我就剽竊吧。?
好。最簡單的原子寫出來了。希望到現(xiàn)在,你還是會(huì)覺得:“介,介有什么呀?”?
下面來看組合規(guī)則。都可以弄些什么組合規(guī)則呢?讓我來掰著腳指頭數(shù)一數(shù)。?
有一點(diǎn)作為thumb of rule需要注意的:每個(gè)組合規(guī)則盡量簡單,并且 只做一件事 。?
1。順序。把若干個(gè)logger對象順序?qū)懸槐?#xff0c;自然是一種組合方式。?
Java代碼??class?SequenceLogger?implements?Logger{?? ??public?void?println(int?level,?String?msg);{?? ????foreach(l:?loggers);{?? ??????l.println(level,?msg);;?? ????}?? ??}?? ??public?void?printException(Throwable?e);{?? ????foreach(l:loggers);{?? ??????l.printException(e);;?? ????}?? ??}?? ??private?final?Logger[]?loggers;?? ??SequenceLogger(Logger[]?ls);{?? ????this.loggers?=?ls;?? ??}?? }??
2。邏輯分支。當(dāng)消息的重要程度等于某一個(gè)級別的時(shí)候,寫logger1,否則寫logger2。?
Java代碼??class?FilteredLogger?implements?Logger{?? ??private?final?Logger?logger1;?? ??private?final?Logger?logger2;?? ??private?final?int?lvl;?? ??public?void?println(int?level,?String?msg);{?? ????if(level==lvl);logger1.println(level,?msg);;?? ????else?logger2.println(level,?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????if(lvl==ERROR);?logger1.printException(e);;?? ????else?logger2.printException(e);;?? ??}?? }??
為了簡潔,下面的例子我就不寫構(gòu)造函數(shù)了。?
3。忽略。當(dāng)消息的重要程度大于等于某一個(gè)值的時(shí)候,我們寫入logger1,否則寫入logger2。?
Java代碼??class?IgnoringLogger?implements?Logger{?? ??private?final?Logger?logger1;?? ??private?final?Logger?logger2;?? ??private?final?int?lvl;?? ??public?void?println(int?level,?String?msg);{?? ????if(level>=lvl);logger1.println(level,?msg);;?? ????else?logger2.println(level,?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????if(lvl<=ERROR);?logger1.printException(e);;?? ????else?logger2.printException(e);;?? ??}?? }??
其實(shí),2和3本來可以統(tǒng)一成一個(gè)組合規(guī)則的,都是根據(jù)消息重要程度來判斷l(xiāng)ogger走向的。如果我用的是一個(gè)函數(shù)式語言,我會(huì)毫不猶豫地用一個(gè)predicate函數(shù)來做這個(gè)抽象。?
可惜,java中如果我要這么做,就要引入一個(gè)額外的interface,還要多做兩個(gè)adapter來處理ignore和filter,就為了節(jié)省一個(gè)類和幾行代碼,這樣做感覺不是很值得,所以就keep it stupid了。?
對了。我說“CO不看需求”了么?如果說了,對不起,我撒謊了。?
CO確實(shí)不是一個(gè)以需求為中心的方法論。它有自己的組合系統(tǒng)要關(guān)心。?
但是需求也仍然在CO中有反映。我們前面提出的那些需求比如打印系統(tǒng)時(shí)間什么的不會(huì)被無中生有地實(shí)現(xiàn)。?
只不過,在CO里面,這些需求不再影響系統(tǒng)架構(gòu),而是被實(shí)現(xiàn)為一個(gè)一個(gè)獨(dú)立的組合子。同時(shí),我們拋開需求之間復(fù)雜的邏輯關(guān)系,上來直奔主題而去就好了。?
4。對exception直接打印getMessage()。?
Java代碼??class?ErrorMessageLogger?implements?Logger{?? ??private?final?PrintWriter?out;?? ??private?final?Logger?logger;?? ??public?void?println(int?level,?String?msg);{?? ?????logger.println(level,?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ?????out.println(e.getMessage(););;?? ??}?? }??
5。對了,該處理NeptuneException了。如果一個(gè)exception是NeptuneException,打印execution trace。?
Java代碼??class?NeptuneExceptionLogger?implements?Logger{?? ??private?final?PrintWriter?out;?? ??private?final?Logger?logger;?? ??public?void?println(int?level,?String?msg);{?? ????logger.println(level,?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????if(e?instanceof?NeptuneException);{?? ??????((NeptuneException);e);.printExecutionTrace(out);;?? ????}?? ????else{?? ??????logger.printException(e);;?? ????}?? ??}?? }??
6。對EvaluationException,照貓畫虎。?
Java代碼??class?JaskellExceptionLogger?implements?Logger{?? ??private?final?PrintWriter?out;?? ??private?final?Logger?logger;?? ??public?void?println(int?level,?String?msg);{?? ????logger.println(level,?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????if(e?instanceof?EvaluationException);{?? ??????((EvaluationException);e);.printEvaluationTrace(out);;?? ????}?? ????else{?? ??????logger.printException(e);;?? ????}?? ??}?? }??
7。在每行消息前打印系統(tǒng)時(shí)間。?
Java代碼??class?TimestampLogger?implements?Logger{?? ??private?final?Logger?logger;?? ??public?void?println(int?level,?String?msg);{?? ????logger.println(level,?new?Date();.toString();+":?"?+?msg);;?? ??}?? ??public?void?printException(Throwable?e);{?? ????logger.println(ERROR,?new?Date();.toString();+":?");;?? ????logger.printException(e);;?? ??}?? }??
這個(gè)類其實(shí)可以再注射近來一個(gè)DateFormat來控制日期格式。但是這不是重點(diǎn),我們忽略掉了它。?
可是,這個(gè)類有一個(gè)很別扭的地方,在printException中,我們必須要把系統(tǒng)時(shí)間單獨(dú)打印在一行,然后再打印異常信息。?
這可不是我們想要的效果。我們本來是想讓系統(tǒng)時(shí)間出現(xiàn)在同一行的。?
怎么辦?回頭看看,如果Logger接口多一個(gè)print函數(shù),一切就迎刃而解了。重構(gòu)。?
Java代碼??class?TimestampLogger?implements?Logger{?? ??private?final?Logger?logger;?? ??private?boolean?freshline?=?true;?? ??private?void?printTimestamp(int?level);{?? ????if(freshline);{?? ??????logger.print(level,?new?Date();.toString();+":?");;?? ????}?? ??}?? ??public?void?print(int?level,?String?msg);{?? ????printTimestamp(level);;?? ????logger.print(level,?msg);;?? ????freshline?=?false;?? ??}?? ??public?void?println(int?level,?String?msg);{?? ????printTimestamp(level);;?? ????logger.println(msg);;?? ????freshline?=?true;?? ??}?? ??public?void?printException(Throwable?e);{?? ????printTimestamp(ERROR);;?? ????logger.printException(e);;?? ????freshline?=?true;?? ??}?? }??
當(dāng)然,前面的一些組合子也都要增加這個(gè)print函數(shù)。相信這應(yīng)該不難。就留給大家做個(gè)練習(xí)吧。?
好啦。到現(xiàn)在,我們手里已經(jīng)有兩個(gè)基本組合子,7個(gè)組合規(guī)則。應(yīng)該已經(jīng)可以組合出來不少有意義沒意義的東西了。?
為了避免寫一大堆的new和冗長的類名字,我們做一個(gè)facade類,來提供簡短點(diǎn)的名字,節(jié)省我們一些鍵盤損耗。
Java代碼??class?Loggers{?? ??static?Logger?nop();{return?new?NopLogger();;}?? ??static?Logger?writer(PrintWriter?writer);{?? ????return?new?WriterLogger(writer);;?? ??}?? ??static?Logger?writer(OutputStream?out);{?? ????return?writer(new?PrintWriter(out,?true););;?? ??}?? ??static?Logger?filter(int?lvl,?Logger?l1,?Logger?l2);{?? ????return?new?FilteredLogger(lvl,?l1,?l2);;?? ??}?? ??static?Logger?ignore(...);{...}?? ??static?Logger?timestamp(...);{...}?? ??...?? }??
這樣,在用這些組合子的時(shí)候大概代碼能夠好看一點(diǎn)吧。?
"你丫還有沒有點(diǎn)實(shí)在的東西呀?"?
要是我,可能也早就忍不住了。?
好,好。我其實(shí)并沒有忘記前面說的那個(gè)logging的例子。賣了這么長時(shí)間的關(guān)子,除了有想形而上的虛榮心外,也是想給大家多一點(diǎn)時(shí)間來嚼一下這個(gè)例子,讓熟悉OO的朋友肚子里面多少有個(gè)腹稿。?
下面,我來繼續(xù)上回書說到的這個(gè)logging。?
前面列舉了那么一大堆亂七八糟的需求,不知道是不是有人和我一樣看著這些繁雜的條目鬧心。我在做的時(shí)候其實(shí)只想了五六條需求,就已經(jīng)開始煩了。何況還有一些暫時(shí)不知道如何抉擇的幾個(gè)疑問點(diǎn)。最初Kiss出來的那個(gè)logger實(shí)現(xiàn)明顯不能用了。refactor的價(jià)值也有限。?
怎么辦?智慧果也許給了我們智慧,但是這智慧畢竟是有限的。側(cè)頭看去,驀然看見荊棘里跳動(dòng)的火焰,那是上帝的光芒么??
好,既然這條路看來比較坎坷,換條路試試看也未嘗不可。走這條路的好處在于,我們可以暫時(shí)不用理會(huì)這些討厭的“需求”了。CO這個(gè)方法論不是以需求為中心,而是從底層的組合子出發(fā),就像小孩子擺弄積木,我們只管發(fā)揮自己的想象力,看看我們能擺弄出些什么東西來就好了。?
如果一切順利,我們希望能夠跨越瀚海高山,走到流著奶和蜜的土地。所有這些亂七八糟的需求,我們希望他們能夠被簡單地配置,或者至少能夠通過聲明式的方法來“聲明”,而不是通過過程式的方法來“實(shí)現(xiàn)”。?
出發(fā)之前,鼓舞以下士氣先。?
前面charon說,這種東西類似公理系統(tǒng)。確實(shí),非常非常類似。不過,門檻并不像這個(gè)名字聽來那么嚇人的高。我們并不需要一下子就完全把完備性考慮清楚,我們不是上帝,沒有這么大本事。?
類似于xp + refactoring,我們的組合子的建造也一樣可以摸著石頭過河,走一步看一步的。?
比如,假如我的Logger接口定義的時(shí)候沒有print函數(shù)而只有println,那么后面我們會(huì)發(fā)現(xiàn)在制造打印當(dāng)前時(shí)間的組合子的時(shí)候會(huì)出現(xiàn)麻煩。不過,沒關(guān)系,等到意識(shí)到我們需要一個(gè)print()函數(shù)的時(shí)候,回過頭來refactor也是可以的。?
為了說明這點(diǎn),我們現(xiàn)在假設(shè)Logger接口是這樣:?
Java代碼??
好,路漫漫而修遠(yuǎn)兮,我們就這樣按照上帝的昭示上路吧,雖然前路云封霧鎖。?
首先,任何組合子系統(tǒng)的建立,都起步于最最基本最最簡單的組合子。然后再一步步組合。?
比如,布爾代數(shù)必然起步于true和false;自然數(shù)起步于0,1,然后就可以推演出整個(gè)系統(tǒng)。?
那么,對Logger來說,有0和1嗎??
有的。?
0就是一個(gè)什么都不做的Logger。?
Java代碼??
(注,不要小看這個(gè)什么都不做的Logger,它的作用大著呢。你能想象一個(gè)沒有0的整數(shù)系統(tǒng)嗎?)?
什么是1呢?一個(gè)直接向文件中打印信息的應(yīng)該就是一個(gè)1了。?
偽碼如下:?
Java代碼??
那么,怎么實(shí)現(xiàn)這個(gè)類呢??
這里,我們遇到一個(gè)東西要抉擇,文件打開是要關(guān)閉的。那么誰負(fù)責(zé)打開文件?誰負(fù)責(zé)關(guān)閉?是否要在構(gòu)造函數(shù)中new FileOutputStream()?,然后再提供一個(gè)close()函數(shù)來close這個(gè)stream??
答案是:不要。?
無論是ioc也好,還是CO的組合方法也好,一個(gè)原則就是要盡量保持事情簡單。那么,什么最簡單?我們此處真正需要的是什么?一個(gè)file么??
不,其實(shí)一個(gè)PrintWriter就足夠了。?
當(dāng)然,最終文件必須還是要通過代碼打開,關(guān)閉。但是,既然反正也要打開文件,我們這里不去管也不會(huì)增加什么工作量,對么?那么為什么不樂得清閑呢??
“先天下之憂而憂”這種思想是最最要不得的。?
于是,最簡單的1應(yīng)該是這樣:?
Java代碼??
“哈!還說什么CO。你這不是剽竊ioc pattern嗎?”?
完了,被抓個(gè)現(xiàn)形。還真是ioc pattern。其實(shí),世界上本來沒有什么新東西,所謂“方法論”,本來是一種思考方法,而不是說形式上必然和別人不同。不過,說我剽竊,我就剽竊吧。?
好。最簡單的原子寫出來了。希望到現(xiàn)在,你還是會(huì)覺得:“介,介有什么呀?”?
下面來看組合規(guī)則。都可以弄些什么組合規(guī)則呢?讓我來掰著腳指頭數(shù)一數(shù)。?
有一點(diǎn)作為thumb of rule需要注意的:每個(gè)組合規(guī)則盡量簡單,并且 只做一件事 。?
1。順序。把若干個(gè)logger對象順序?qū)懸槐?#xff0c;自然是一種組合方式。?
Java代碼??
2。邏輯分支。當(dāng)消息的重要程度等于某一個(gè)級別的時(shí)候,寫logger1,否則寫logger2。?
Java代碼??
為了簡潔,下面的例子我就不寫構(gòu)造函數(shù)了。?
3。忽略。當(dāng)消息的重要程度大于等于某一個(gè)值的時(shí)候,我們寫入logger1,否則寫入logger2。?
Java代碼??
其實(shí),2和3本來可以統(tǒng)一成一個(gè)組合規(guī)則的,都是根據(jù)消息重要程度來判斷l(xiāng)ogger走向的。如果我用的是一個(gè)函數(shù)式語言,我會(huì)毫不猶豫地用一個(gè)predicate函數(shù)來做這個(gè)抽象。?
可惜,java中如果我要這么做,就要引入一個(gè)額外的interface,還要多做兩個(gè)adapter來處理ignore和filter,就為了節(jié)省一個(gè)類和幾行代碼,這樣做感覺不是很值得,所以就keep it stupid了。?
對了。我說“CO不看需求”了么?如果說了,對不起,我撒謊了。?
CO確實(shí)不是一個(gè)以需求為中心的方法論。它有自己的組合系統(tǒng)要關(guān)心。?
但是需求也仍然在CO中有反映。我們前面提出的那些需求比如打印系統(tǒng)時(shí)間什么的不會(huì)被無中生有地實(shí)現(xiàn)。?
只不過,在CO里面,這些需求不再影響系統(tǒng)架構(gòu),而是被實(shí)現(xiàn)為一個(gè)一個(gè)獨(dú)立的組合子。同時(shí),我們拋開需求之間復(fù)雜的邏輯關(guān)系,上來直奔主題而去就好了。?
4。對exception直接打印getMessage()。?
Java代碼??
5。對了,該處理NeptuneException了。如果一個(gè)exception是NeptuneException,打印execution trace。?
Java代碼??
6。對EvaluationException,照貓畫虎。?
Java代碼??
7。在每行消息前打印系統(tǒng)時(shí)間。?
Java代碼??
這個(gè)類其實(shí)可以再注射近來一個(gè)DateFormat來控制日期格式。但是這不是重點(diǎn),我們忽略掉了它。?
可是,這個(gè)類有一個(gè)很別扭的地方,在printException中,我們必須要把系統(tǒng)時(shí)間單獨(dú)打印在一行,然后再打印異常信息。?
這可不是我們想要的效果。我們本來是想讓系統(tǒng)時(shí)間出現(xiàn)在同一行的。?
怎么辦?回頭看看,如果Logger接口多一個(gè)print函數(shù),一切就迎刃而解了。重構(gòu)。?
Java代碼??
當(dāng)然,前面的一些組合子也都要增加這個(gè)print函數(shù)。相信這應(yīng)該不難。就留給大家做個(gè)練習(xí)吧。?
好啦。到現(xiàn)在,我們手里已經(jīng)有兩個(gè)基本組合子,7個(gè)組合規(guī)則。應(yīng)該已經(jīng)可以組合出來不少有意義沒意義的東西了。?
為了避免寫一大堆的new和冗長的類名字,我們做一個(gè)facade類,來提供簡短點(diǎn)的名字,節(jié)省我們一些鍵盤損耗。
Java代碼??
這樣,在用這些組合子的時(shí)候大概代碼能夠好看一點(diǎn)吧。?
好了,走了這么遠(yuǎn),(雖然很多人可能還是覺得根本沒看見什么激動(dòng)人心的東西吧?都是簡單得不能再簡單的小類,有什么了不起的呢?)。讓我們停下腳步,看看這么信馬由韁把我們帶到了哪里吧。?
from:?http://ajoo.iteye.com/blog/23306
總結(jié)
以上是生活随笔為你收集整理的论面向组合子程序设计方法 之 燃烧的荆棘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 论面向组合子程序设计方法 之 失乐园 之
- 下一篇: 面向组合子程序设计方法 之 新约