唧唧歪歪一大堆??隙ㄔ缬腥瞬荒蜔┝?。
"你丫還有沒有點實在的東西呀?"
要是我,可能也早就忍不住了。

好,好。我其實并沒有忘記前面說的那個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ī)則來看看能不能對付前面提出的那些亂七八糟的需求。