任何標題上提及「已死」二字的文件,多半為了達到「標題殺人」以吸引目光,希望挑動那些被宣判已死對象信仰者最敏感的神經,發出聚集圍觀的信號,讓贊同者頜首稱是。

實際上,當一個對象被宣告已死,多半不是指沒有人在應用它,反之,代表它遭到大量、過度、僵化的應用,脫離它原先提出之本意,而且,被宣判已死之對象,往往日後依然盛行,那麼設計模式呢?

這邊談的就是四人輩(Gang of four, Gof)出版的《Design Patterns: Elements of Reusable Object-Oriented Software 》中提及之23個設計模式,而不是泛指某些事物反覆出現而形成的模式或各式衍生物。設計模式從1994出版至今近20個年頭,對該書內容支持者與反對者間的爭論交錯不休。例如,《Coders at work》中首位受訪者Jamie Zawinski毫不客氣地談到:「我認為這本書是一派胡言(was crap),給人的感覺好像程式設計只要剪貼就能搞定」。Ruby語言創建者在《松本行弘的程式世界》中,也談到他對設計模式該書的第一印象:「造成這麼大話題的,居然是這麼理所當然的東西」、「有些模式在C++、Java這類靜態定型語言中很有效,但在Smalltalk與Ruby這類動態定型語言之中,並沒有多大意義」。

Peter Norvig在1996年的〈Design Patterns in Dynamic Programming〉演講中,指出:「設計模式某種意義上,是為了解決物件導向語言本身缺陷的權宜之計」。而Slava Akhmechet在〈Functional Programming For The Rest of Us〉中,談到:「無論使用哪種語言,模式適用於所有軟體工程…這絕非事實」。

Gof之一Ralph Johnson在設計模式發表15週年的訪談〈Design Patterns 15 Years Later〉也談到:「這些語言不需要設計模式中的某些模式,這些語言提供了問題的替代解決方案,我們的模式是針對C++與 Smalltalk間的語言,包括那些被稱為物件導向的語言」!

就像上述的各種意見,想說設計模式壞話,並不乏名言佳句。在看過這些之後,只要再下個「設計模式已死」的結論,你就相信了嗎?

媒體就是這樣,為了支持某個論點,僅剪裁對該論點有利的部份來支持它,只要握有發表權,在想要擁護某個觀點,就會這麼做,在你開始相信設計模式已死的前一刻,別忘了追溯那些引言的出處,察看其始末,就像我在前頭巧妙安排了一堆故事,讓你幾乎相信是這麼一回事。幸運地,我留下這些引言的出處,請看完全文,再來決定你的選擇。

流暢API、惰性與設計模式

既然設計模式一書已出版近20個年頭,那麼在實作形式上有很大的不同就會是必然,Erich Gamma曾直言:「大家很容易忽略的是,我們寫設計模式時,連Java和C#都還沒有」,然而Java是最大力推廣了設計模式的功臣之一,除了在Java SE API中,隨處可見傳統設計模式實作形式的原始碼外,最新出爐的JDK8中,融合了一些新的設計風格,設計模式也有了新的實作樣貌,儘管乍看不像23種設計模式。

這陣子API設計的流行風格之一是流暢API,在實現時,骨子裏仍有不少部分是使用了設計模式的概念。

舉JDK8中的Comparator實例建立方式之一為例,無論是nullsFirst(naturalOrder())或naturalOrder().reversed()都實現了「Decorator模式」,儘管前者被包裹的對象是以引數形式傳入,而後者是直接取自物件本身(this),而兩者都見不到建構物件的語法;使用流暢API實現「Builder模式」,在Java界也不算新鮮事了,我先前專欄〈Regex的強大與哀愁〉談到的VerbalExpressions,雖使用了方法鏈串new VerbalExpression.Builder().find("<a").something().find("><img").something().find("></a>").build()的外觀,實際在概念上就是實現了Builder模式。

在一些需要效率的場合中,有些API實現了惰性的概念,在我先前專欄〈產生器與惰性求值〉中就談過,可使用「Iterator模式」來實現產生器概念,從而讓addOne(addOne(addOne(list)))可以實現惰性求值(Lazy evaluation),而在使用Stream API的場合,像是stream.map(elem -> elem.length()).filter(length -> length > 10).findFirst()也實現了惰性求值,Logger在JDK8時所新增的一些方法,也具有惰性概念,例如logger.debug(() -> expansiveLogging()),無論是Stream的map、filter或是Logger的debug等方法,基本上都是「Template mathod模式」的實作延伸。

實作模式的方法本來就不僅一種,隨著新設計風格的出現,也就有了不少使用設計模式概念,但外貌上與23種設計模式完全不像的實作。若沒有仔細去考量待解的問題,只想找出幾個相近的模式直接套用,那設計模式這本書就成了Jamie Zawinski說的:「那根本就不是程式設計,而是著色書了」。

語言元素與設計模式

方才談到Template method模式,設計模式該書中使用了C++來示範該模式的實作。C++支持多重繼承,然而,現今不少物件導向語言僅支持單一繼承,像是Java,在這類語言中使用繼承實作Template method模式,多半也限制了它應用的場合,然而在JDK8中,讓類別去實作某個具有預設方法的介面,其實就是在實現Template mathod模式,雖然在Java的術語上稱為實作介面,實際上的確就是在做多重繼承。

JDK8中Stream API的filter、map等方法,實際上是從Template method模式延伸出來的Template callback模式,然而過去匿名類別語法囉嗦,只會在利大於弊,像是實現惰性取得效率的好處大於可讀性損失的壞處下才會採用,在JDK8提供了Lambda語法之後,因為可讀性的提升,才使得Template callback模式得到了大量的採用。

事實上,在Ruby這類語言中,使用模組(module)來實作Template method早已屢見不鮮,在JavaScript這類具備一級函式概念的語言中,早已見Template callback模式的實現,通常也只簡稱為Callback模式,甚至因為還衍生出Continuation-passing style(CPS)與Promise等更進階的風格,以避免Callback hell的問題,這在我先前專欄〈非同步操作的多種模式〉中曾經探討過。

語言中的元素確實會影響設計模式的實現方式與意願,某些語言的元素會在實現某些設計模式簡單到令人意外,然而就如Ralph Johnson談到的‥「我不認為其他語言的程式設計師不需要模式,他們只是使用了不同的模式集合」,即使Ruby語言有不少奇妙的元素,仍然有書以Ruby語言實作來介紹設計模式,就算是在JavaScript這門動態定型、弱型別、基於原型的語言中,Stoyan Stefanov仍在《JavaScript Patterns》用了一整章探討設計模式的實現,因為他認為:「對你而言,熟悉它們,談論它們都是有益的」。

松本行弘談到設計模式一書的本質是:「為經常用到的實用模式決定適切的名稱,使它們成為設計時的語彙」,這讓大家能意識到模式的存在,並能作為溝通的基礎。事實上,設計模式的實作外觀,在20年後已經有很大的不同,程式設計仍會不斷演進,無論是在語言或風格上,終有一天也許我們會完全對比不出設計模式書中的實作樣貌,在這種情況下,設計模式是否已經死亡,就端看屆時我們是否仍以設計模式中提及的語彙來溝通。

想想現階段,當你與他人討論著如何實現流暢API、惰性等這類近來流行的風格時,你又會怎樣描繪你的思考或實作起點?

那麼設計模式不死囉?我可以特意將結論導向這個方向,端看你是否會做獨立思考,能否會想到這些是我特意裁剪過的資訊,然而我們真正該做的,是在傳達經典設計模式之後,多探討一些結合現代風格的實作,以彌補與反映這20年來又累積下來的經驗,如此一來,即使未來程式設計演進到另一更高層次,使得我們不再以程式創建、行為、結構為思考起點,不再以設計模式語彙進行溝通,屆時設計模式是否已死也不會是個重點了!

專欄作者

熱門新聞

Advertisement