GOF提出的設計模式,其本質思想是封裝變化。故而,建立型模式封裝的是物件建立的變化,結構型模式封裝的是物件之間的協作與組合結構,行為型模式則封裝了物件行為的變化。所謂“行為”,不正是函式所能要表達的嗎?在支援FP的程式語言中,函式成為了一等公民,即它可以脫離物件而單獨存在,也能夠當做引數或返回值(高階函式)。於是,傳統的OO設計模式開始了變化,尤其是行為型模式。
函式的抽象能力
從函式的抽象角度看,任何行為都可以理解為是一個對型別進行轉換的函式,這是FP思想對OO設計模式的最大沖擊。例如Strategy模式與Command模式,前者封裝了演算法策略的變化,後者則封裝了命令請求的變化。無論演算法策略,還是命令請求,都可以表現為一個函式。
譬如說將各種四則運算看做是一種演算法策略,為了應對具體計算的變化,在Java中我們應該定義四則運算的策略介面:
public interface Strategy {
int compute(int a, int b);
}
public class Context {
private final Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void use(int a, int b) {
strategy.compute(a, b);
}
}複製程式碼
接收兩個整數,然後經過計算返回一個整數。顯然,四則運算的呼叫者其實關注的不是Strategy
這個介面,而是compute
這個行為。跟進一步,呼叫者其實關注的是將兩個整數轉換為一個整數的行為,他並不關心介面是什麼,函式名有是什麼,而是關注f(a, b) = c
這個函式。於是,在Scala中,策略模式的實現就變為:
class Context(f: (Int, Int) => Int) {
def use(a: Int, b: Int): Int = f(a, b)
}複製程式碼
當然,你可以可以為這個函式定義一個型別,使其更加表意:
type Stategy = (Int, Int) => Int複製程式碼
當然,如果面對的是一組策略行為的封裝,且這些策略行為的變化是一致的,使用一個介面將這些行為封裝起來,在重用和表意角度講,似乎又比單純使用函式更佳。Scala提供OO與FP兩種正規化,算是一種騎牆的取巧,程式設計師需要依勢而為。Scala給你提供了豐富而精彩的食材,如果你沒有將菜做得色香味俱全,不能怪食材不好,還是自己太爛了。
Scala還提供了一種類似block的機制,稱之為by name call。它接受的是一個語句塊,而非函式型別。所以要注意這種形式與無參函式的區別。此外,by name call同時還具有延遲呼叫的能力。例如,當我們定義一個invoke
函式接受一個無傳入引數的函式時:
def invoke(f: () => Unit) = f()複製程式碼
如果你向invoke
傳入println("scala")
,scala會報告錯誤:
這是因為println("scala")
返回的是Unit
型別,而不是() => Unit
函式型別。使用by name call就沒有這個問題:
f: => Unit
是一個語句塊,所以不能像函式那樣呼叫。我們可以使用這種方式來快捷實現Command模式。
由於Java 8已經支援Lambda表示式,雖然它仍然不支援高階函式,但是作為Java程式設計師,仍然有必要培養函式抽象的能力與習慣。在Java 8中使用Lambda,不僅讓語法變得簡潔,還可以讓呼叫者可以脫離對具體某個介面的依賴,而僅僅依賴函式的抽象特徵。
函式的組合能力
FP的程式設計思想中,除了高階函式(包括Curry等)具有的抽象能力之外,還有一個好處是提供組合子能力。落實到Scala的語法上,就是偏函式(Partial Function)的andThen
,compose
與orElse
。
Pavel Fatin在文章《Design Patterns in Scala》用OO設計模式中的Chain of Responsibility(職責鏈)模式來對比組合子,其實還是比較牽強的。或者說,FP思想中的組合子遠遠比職責鏈模式更強大。在Elixir語言中,甚至還提供了管道操作符|>
實現這種函式的組合。而我在部落格《Scala中的Partial Function》中已經非常詳解地介紹了Scala的偏函式,大家可以移步閱讀。
如果真要對比,那麼結合Scala的語法來看,則orElse
可以非常方便地模擬職責鏈模式,而andThen
則近似於管道-過濾器模式。其實我在OO語言中,很少運用GOF標誌的職責鏈模式,也就是當尋找到具體職責的承擔者時,履行職責後即可退出的方式;而是對這種模式進行調整,讓其在履行職責後繼續執行next
的職責,又近乎於管道-過濾器了。
所以說,設計模式的運用妙乎於心,講究應勢而變。在融入FP思想後,要從本質思想去面對這些模式,不拘泥於OO還是FP,似乎才是未來程式設計的取捨之道。