有人問我 Design Pattern

我說,Design Pattern 對軟體工程師而言,就像是在格鬥電玩裡頭所謂的「連續技」(Combo)那樣的東西。

你學會了開發工具的操作與程式語言,像是學會了如何使用搖桿上每個按鈕的功能,你開始接觸函式庫,像是選擇了要使用的角色,你學會某個函式如何使用,就像是學會了某個招式。

接下來,你發現將某些招式組合起來,可以發揮更大的威力,像是,如果你玩格鬥天王系列,就知道如果讓草薙京把對手踢到空中的時候,就可以施展大蛇薙,這,我們稱之為連續技;如果你平常就熟練這些連續技,又清楚出招的時機,經常可以有效傷害對方,取得不錯的成績。

Design Pattern 就是這些年來許多軟體工程師所整理出來,在某些情況下,你可以如何用某些方式組合函式與物件之間的關係,並且給了這些關係特定的名字,讓在你日復一日的開發工作中,在遇到某些類似的時機可以用上,而且提供了一套現成的詞彙,方便你說明你做了什麼,或是打算怎麼做某個東西。

有人問我會不會注重、應不應該注重 Design Pattern,這個問題實在有點奇妙…我還沒有看過多少人玩格鬥電玩不練連續技,但也沒有看到有人每次在對戰中,都非要把某套連續技施展出來不可。

我們先把書本上那些第一次看很容易讓人迷惑的字眼丟開,來看一下物件之間會有哪些關係。比方說,今天所有人都想把某件事情交給某個人來做,而能做這件事情的,只能是這個人,所有人都只找這一個人,這種多對一的關係,叫做 Singleton。

今天我沒有要別人特別去做什麼事,但有個人在我做了某件事之後,我的某個狀態改變之後,他就會自己去做某件事,做這件事情的時候他也不必告訴我。甚至,當我做了某件事情,會自己做別的事情的人還不只一個。這種一對多的關係,叫做 Observer。

今天有一件事情雖然我可以自己做,但假如這個時候出現了一個特別的人,當我要做這件事情的時候,我可以問他我該怎麼做,或是當我告訴他的時候,他可以因此做某件事情。這種一對一的關係,叫做 Delegate。

今天我有一件事情要找個人來做,我只關心我要做什麼事,而不關心到底是做事的到底是怎樣的人,這個人怎樣做事,所以我要有一個窗口,告訴窗口,我想要寄信,窗口就給了我一位郵差,我生病了,窗口就給我一位醫生,我不用自己去找郵差或是醫生,而這個窗口也有可能會派醫生幫我寄信,但只要這個醫生會寄信,我也不用關心寄信的到底是郵差還是醫生。這個窗口,叫做 Abstract Factory。

而如果今天我要寄信,我找了郵差,但雖然都是郵差,每個郵差寄信的方式都不一樣,有的開車,有的騎單車,有的信甚至要用航空遞送,所以我必須自己去找一個最適合我的郵差幫我寄信,這叫做 Adapter。當然我也可以找某個窗口,告訴他我的信要用平信、掛號還是要寄到國外,然後只要給我一個郵差就好,而這就是上面提到的 Abstract Factory。

而如果一件事情很複雜,需要好幾個人分工合作,你不想要自己去找所有人,而是只想找一個人幫你協調其他幾個人的工作,有什麼事情都只找這個人就好,這就叫做 Facade—有時候我在想這邊為什麼不用個更好懂一點的名稱,像是管理員之類的,我寫過的 Facade 往往叫做什麼什麼 Manager。因為 Facade 統一事權,所以 Facade 往往也是 Singleton。

這邊列出幾個常用 Pattern,書上列出的一些 Pattern 不見得實務上會常用到,某些 Pattern 也不見得會出現在書上,就像電玩攻略也不會列出所有的連續技,而每個人玩遊戲也都可能發展出各自的連續技。而我以為,知道這些連續技是一回事,而其實搞清楚什麼時候該用什麼,會是更重要的事情。

在一些格鬥遊戲中,會有 Sudden Death 的模式,雙方誰只要被打到一下就輸了,這個時候想來就不需要打出一套連續技出來。在工作中,也往往需要寫一些簡短、臨時、快速解決某些小問題的 Script,在這種狀況下還堅持要用 Design Pattern,要符合某個 Pattern 的作法,就沒什麼道理。

熟悉、並使用 Design Pattern 的優點,除了在於讓你知道有套連續技可以使用之外,我想在於解決軟體專案成長時的痛苦。前面提到,Design Pattern 其實就是物件間各種可能的關係,使用 Design Pattern,也就意味著可以讓物件間的關係可以得到管理,避免錯綜複雜的參照,用別的說法來說,管理物件的關係就是降低耦合,降低耦合的目的則是為了適應變化。

軟體專案絕對不會孤立地成長,而是在世界的變化中掙扎。有些是技術的改變,作業系統、瀏覽器、程式語言每年都在升級,前一年最好的實作方式可能隔年就過時了。

有更多是需求與商業邏輯的改變,什麼東西可以在電腦上面可以執行運作,還是要有點道理,雖然很多時候你對某段程式為什麼會有 bug,或為什麼會動,也覺得沒什麼道理。但,什麼東西熱門、產生什麼需求、什麼東西會賺錢,雖然還是有人嘗試分析出一些道理,不然那麼多 3C 網站要寫什麼呢?但從你的眼睛來看,又往往是沒什麼道理的;而軟體工程師的工作,就是把沒道理的商業邏輯變成稍微有一點道理的程式碼。

今年的需求可能是要送出的連線愈少愈好,避免造成 server 癱瘓,隔年的需求就變成能送出多少連線就送出多少,好快速抓取大量的檔案。今年畫面要有栩栩如生的光影效果,隔年開始提倡扁平化設計…。

這也代表,今年你寫下的程式碼,可能隔年就要大幅度改寫,甚至整個拿掉,如果專案不在良好的管理下成長,有多少地方用到那個物件都搞不清楚,專案的負責人可能換人,一段程式該不該拿掉、能不能拿掉,接手的人也都搞不清楚,慢慢地專案中就開始累積許多無用的垃圾,導致開發週期愈來愈長,無法如期推出新版,甚至讓你錯過了商業的時機,或是推出新版本之後,裡頭有一大堆 Bug。

你沒有辦法預測未來會有什麼變化,但,你可以盡可能做到,怎樣讓當你想刪除一段程式碼時,可以跟加入這段程式碼的時候一樣容易。

現在很多工具背後的目的都是適應變化。分散式版本管理系統讓你可以開好幾個開發分支,可以同時往多個可能的變化方向發展;在變化發生時會改寫程式碼,可能會把原本正確的邏輯改壞了,所以會需要大量的單元測試保證程式沒有寫錯,使用測試驅動開發。而執行單元測試這件事情,應該是每次改動程式碼都要做,這樣的事情應該自動化,所以大家開始導入持續整合系統。Clean Code 啦、Refactory 啦、Agile 啦…這些書在講的都是如何適應變化。

話說《精實創業》不是說,應該要用最短的時間做出 MVP,即早拿到市場上了解需求,確認產品的商業價值,不然就會導致花了大量成本,卻做出沒有效益的產品?一開始可能確實是這樣,但是一套軟體的 1.0 版到 2.0 版之間,應該要採取完全不一樣的心態、工具以及方法。

就像你在格鬥遊戲中面對不同對手會有不同的打法,你在處理不同的專案與問題時,也會有不同的策略;很多事情在動手寫程式之前就要自己想過一輪,你的老闆與專案經理才不會管你的死活,不會管你怎麼把沒道理的事情變得有道理,只會轉達外部沒道理的需求與內部沒道理的想像。

而到底怎樣的物件間應該建立怎樣的關係?最後還是要回到這段程式碼、這個物件到底是什麼,負責什麼,每個物件的角色決定之後,慢慢他與其他物件的關係就豁然開朗;而更之前要問的問題,可能會是,我們有沒有可能把一些原本以為應該要插入到既有程式碼的部份,抽離出來,封裝成獨立的物件。

比方說,今天我們有個電子書的軟體,裡頭有個商業促銷的規則,當用戶讀過十本書的時候跳出訊息希望用戶上 App Store 或 Google Play 評分,讀過二十本書的時候,則出現訊息希望用戶上 Facebook 按讚,這個規則只適用在這個版本,三個月之後的下一個版本,要拿掉這條規則。

你有幾種選擇—直接把程式碼寫在電子書檔案 viewer 的部份,把按讚的邏輯
、讀過幾本書的 counter,與如何解析 epub 格式、處理換頁的程式碼寫在一起;或,你把按讚的規則抽離出來變成一個新的物件,每當 viewer 開啟了新的檔案,這個物件就會收到通知,自己改動讀過多少書的 counter,發現到了特定數量之後,就跳出該出現的訊息,這個物件訂閱了 viwer 開啟檔案的事件,viwer 不知道這個物件的存在。你用上了 Observer Pattern。

最後,據我所知,要學會怎麼玩格鬥電玩,除了看攻略說怎麼打,自己下場打,就是經常在電玩機台後面觀摩人家怎麼打。我小時候就經常因為流連電玩場合看人家怎麼打,被家裡頭抓回去打。

以上。

4 thoughts on “有人問我 Design Pattern

  1. Pingback: [Design Pattern] good link to read | Vincent's thoughts

  2. Pingback: [後端小菜鳥](14)-Design Pattern / Unit Test – M. J. YU

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.