設計模式反映問題的抽象分類


iThome 網站首載:設計模式反映問題的抽象分類

在Gof書中寫到,設計模式是在特定情境中,為解決通用設計問題的自定義物件互動說明。許多開發者將焦點放在解決,模式名稱成了解決方案的代名詞,甚至將說明用的具體程式實當成複製、修改的對象,忽略了設計模式實際上反映語言面對領域需求時的常見問題,這些問題在解決時出現類似的思考流程,而設計模式是將思考流程抽象化後進行分類,每個分類都代表了問題的抽象集合。

瞭解設計模式是鑑古知今

儘管實際面對的程式需求各不相同,然而多數問題存在某些重複的相似性,設計模式是思考問題相似性的過程,將觀察到的相似性從實際問題中抽取出來,討論設計上如何解決抽取出的相似性,以求面對類似的具體問題時,可以有現成的抽象概念可以套用,以降低思考過程的複雜度。

舉例來說,圖形介面開發者在面對使用者版面配置需求時,曾思索過物件間如何組合以提供自由組合版面的彈性,動畫剪輯開發者在面對使用者圖片、影像剪接需求時,曾思索過物件間如何組合以提供自由組合影片的彈性,測試框架開發者也曾思索過物件間如何組合以提供自由組合測試案例的彈性,以上問題的相似性是「自由組合」,解決方案也呈現類似結構與行為,因而抽取出組合(Composite)模式的概念,日後若觀察到問題有自由組合的需求,組合模式就可提供現成抽象概念,免去探索解決方案的複雜過程。

某些程度上,設計模式有點像是管理學原則,許多管理書籍會從過往歷史探索某些公司或機構,從各個角度提出某種關鍵因素,巧妙地與歷史串接在一起來論斷公司或機構的興衰成敗,閱讀管理書籍只是瞭解有哪些角度可以切入管理問題,而不是那些角度就是最佳的觀點或解決方案。在經營中的公司或機構採用某些管理原則或避開某些管理方式,也並不保證公司或機構必然興盛或免於敗亡。

無論是從閱讀文件瞭解設計模式,或是在閱讀程式碼時發覺採用了某些模式,都得先理解需求的情境、問題的核心,也要瞭解文件或程式中出現的設計模式實作,也許僅是可能的解決方案之一,有時同一問題在不同應用程式中採用的模式可能不盡相同,就如同有些管理原則在不同公司體系中可能是相對的矛盾。閱讀管理書籍,是在瞭解不同公司或機構經營時會有哪些問題,瞭解設計模式,是在瞭解應用程式開發過程中可能會遭遇哪些需求,兩者的共同點,都是如何從過往中抓出問題的核心。

設計模式是語言重複用於某領域後必然的產物

Gof模式最初是以基於類別、靜態的語言作為解釋模式的實作語言,一度成為優秀設計和最佳實踐的代名詞,然而隨著一些動態、弱型別甚至基於原型的語言逐漸普及,面對這些本質上更彈性的語言,原本基於類別、靜態的Gof設計模式逐漸成為複雜思路的代表,甚至某些開發者會持有「設計模式代表了語言本身存在缺陷,因而需要在設計上提供抽象思路」。

設計模式代表了語言本身存在缺陷嗎?如果從狹義的設計模式來看,基於類別、靜態語言的設計模式,有時在動態甚至非基於類別的語言看來根本微不足道,也可能有更簡單的設計方式,從這個角度來看,有開發者認為若語言本身提供良好機制,就不太需要特殊的設計。然而從廣義來看,模式是一種重複出現的事件或物件題材,就軟體開發來說,只要語言用於某領域夠久或夠頻繁,就必然出現某些重複設計而形成模式,以模式來聲稱語言存在缺陷,其實暗指該語言用於某領域時必然有其擅長與欠缺之處,缺陷的說法只是相對的觀點,而不是絕對。

廣義的設計模式在思索問題本身,而不是狹義的語言機制。舉例來說,Gof模式中有單例(Singleton)模式,狹義來說,是指在基於類別、靜態語言提供執行環境中獨一無二物件,這點就JavaScript來說,根本就不需要這個模式,因為JavaScript每個物件都是獨一無二,而且JavaScript本身沒有類別的概念,自然無法就狹義上來實現單例模式;然而廣義來說,單例模式意指的問題在於:如何在執行期間無論何時何地,透過某方式取得(或建構)的物件都是同一個實例。不少開發者都曾遇過此類問題,也提出過幾種可實現的模式,例如若想呈現以下結果,你會如何解決?若其他開發者曾看過你的解決方式,也就可以循著相同的思路進行設計:
var s1 = new Singleton();
var s2 = new Singleton();
(s1 === s2); # 最後為true要如何實現?

JavaScript本身沒有類別,因而有幾種模擬類別的模式,從狹義的設計模式來看,那些模擬類別的模式是JavaScript本身存在缺陷的結果,但也正因為JavaScript沒有類別,才得以實現上述的單例模式,那麼該說是語言的缺陷或優點,才得以實現這樣的成果呢?

模式是用於啟發設計而不是複製用的樣版

設計模式本身是現成的抽象概念,為了協助開發者理解,說明模式時會使用具體程式碼實作來呈現結構與行為,然而這些程式碼並非作為樣版,而僅是用來理解模式本身要解決的情境與問題。在解決問題的過程中,透過既有模式來思考問題是否具有相似性,並透過模式來啟發類似的思考流程,從而設計出符合應用程式情境的實作,才能從設計模式本身獲得益處。如果只是將模式說明時的具體程式碼作為樣版複製至應用程式填充,如此運用設計模式只會有害而無益。

說明模式時的程式樣版應只作為思考的起點而不是終點,程式樣版的每個細節都應思考其存在意義為何。對目前問題來說,類別繼承是必要的嗎?介面定義是必要的嗎?要不要使用工廠(Factory)來取代建構式?這樣的思考方式,也正是模式存在的意義,無論是抽象概念或具體實作,與其從零開始思索,不如先從過去常見的問題設計開始著手,有時也會因此而看到同一問題的多種設計方式與優缺點,進而反過頭來由更多角度重新思考問題本身。

開發者間理解設計模式也是為了便於溝通,當開發者使用模式名稱進行溝通時,代表著開發者本身對模式名稱背後代表的問題、情境有所認識,也知道整組的抽象概念可用於解決問題,如果開發者雙方對模式名稱有所共識,當開發者A說到某處也許可用到策略(Strategy)模式時,開發者B就可根據策略模式的抽象概念在腦海中啟發具體實作,而不用開發者A逐行撰寫程式碼來解說何謂策略模式,也就不會因此限制住開發者B的具體實作細節。

瞭解設計模式應著重在理解問題本質

有些開發者初見某些模式有相見恨晚的感覺,其實是由於曾有過類似問題的經驗,驟然遇到經過相當次數驗證的模式而心生感嘆,反思自己對問題的認識不夠深入,未曾從某些角度切入問題;與此相反的是,有時開發者本身經驗不足,無法認清模式背後的問題本質,將模式當成教條套用而不知變通,甚至將某種模式作為實作目標,希望讓程式碼看起來像某種設計模式,而不是實際地解決問題。

回過頭來看Gof模式,有些開發者認為Gof中有些模式具有啟發效果,有些模式則沒有實質意義。面對這樣的言論,應該深思他面對的問題領域為何?使用的語言又是什麼?該問題領域中哪些模式有啟發效果而哪些模式沒有?啟發的效果是什麼?毫無啟發的原因又是為何?