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