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

閱讀排行榜
評論排行榜
|
|