無用的設計模式(上篇)

提到設計模式,有一個非常有意思的現象:
理論學習中,幾乎所有的開發人員都認爲它非常有用很重要。
工作實踐中,絕大部分開發人員在項目中找不到合適的應用場景。
設計模式學了一遍又一遍,卻毫無用武之地。大概設計模式最好的歸宿,就是存在程序員的深深的腦海裏。

難道設計模式真的沒有用了嗎?

關於本文

本文目的,通過對設計模式的本質進行探討剖析,建立起更爲高效的認知模式。最終可以靈活運用設計模式到日常工作中,產出穩定、高效、靈活的業務實現。

內容上分爲上下兩篇,上篇爲理論篇,講述了設計模式一些共識、原則的思考理解等。下篇爲實踐篇,通過一個完整的系統設計實例,對上篇的理論進行驗證和實踐。

一、設計模式到底是什麼?

從因果規律來看,任何事物的發展與誕生,必然有其背景原因。想要摸清設計模式的底細,就要明白設計模式是怎麼來的。

1.1 設計模式誕生背景

時間回到20世紀80年代,當時的軟件行業正處於第二次軟件危機中。根本原因是,隨着軟件規模和複雜度的快速增長,如何高效高質的構建和維護這樣大規模的軟件成爲了一大難題。

軟件複用被認爲是解決這一危機的一條可行路徑,而面向對象的思想則很好的解決了複用問題。設計模式正是在這樣的背景下,伴隨着面向對象編程的興起出現的。

1.2 設計模式的前世

設計模式這個術語,其實並非源自於軟件工程領域。它最早起源於建築學領域,是由哈佛建築學博士Alexander在1977年提出的。

他跟團隊在研究了大量的建築結構後發現,爲了解決同一類建築問題而設計出的高質量建築結構具有某些共性,於是使用“模式語言”來描述它。

他定義每個模式均包含前提條件(適用場景)、目標問題、解決方案三個部分。

創建模式的目的是,複用那些已經實踐成功的建築解決方案。

1.3 設計模式的今生

1994年,以四人組(GoF)自稱的四位資深軟件工程學者,借鑑了這種思想,將模式的概念引入軟件工程領域。GoF總結了23種在開發過程中使用頻率較高的設計模式,合著出版了《Design Patterns - Elements of Reusable Object-Oriented Software》一書。設計模式正式誕生。

軟件領域的設計模式,本質上是基於面向對象設計經驗的總結。 它不是解決所有的問題的銀彈,更多的是一種解決問題的思路。

二、我們需要設計模式嗎?

簡單瞭解了設計模式後,對於文章開頭的問題,你有答案了嗎?設計模式真的沒有用了嗎?我們可以提出一些合理的猜測:

設計模式所代表的經驗已經過時了。 距設計模式的提出已經過去了20多年,軟件領域已經有了長足的發展,有可能經驗已經不適用了,所以日常開發中運用設計模式的場景比較少。

業務軟件開發不需要設計模式。 隨着軟件領域的發展,軟件開發的分工,慢慢朝着細緻化、精緻化方向靠攏,技術本身開始獨立於業務發展。我們用上了各種各樣的功能強大的工具框架,填好模板代碼就能完成業務,似乎業務已經不需要過多設計了。

接下來,我們圍繞這兩個問題一起討論下。

2.1 經驗之談,依然有效

不妨先來回憶一下,我們接觸到設計模式最多的地方是在哪裏?

大部分應該都是在框架(工具類庫等)源碼裏吧。那爲什麼在框架裏會大量使用設計模式呢?

很多人會說,因爲框架需要覆蓋的場景多,不僅要考慮現有功能的持續迭代,還要考慮到後續可能的功能擴展。代碼結構既要足夠穩定,保持可維護性。又要有一定的靈活性,保持可擴展性。

所以,框架通常需要一個比較好的設計。而設計最終體現在代碼中,就是對設計模式的應用。

這說明設計模式所代表的經驗,依然是有效的。

2.2 業務需要設計模式

近10年來,軟件生產效率有了巨大的提升。拋開硬件的因素,很大程度上是因爲面向對象的普及,以及配套工具體系(例如框架)的完善。以至於在日常開發中,很多人會認爲框架解決了所有問題,業務不過是CRUD。

所以在討論業務需不需要設計模式之前,我們有必要先討論下框架是否解決了業務開發的難點?

軟件工程學上,將軟件開發過程中的難題,分爲本質困難和非本質困難。

本質困難是,如何抽象出實體,準確地描述現實業務中複雜的概念結構。非本質困難是,如何通過技術落地實現這些概念結構。

框架(編程語言也是)所解決的是非本質困難,是技術落地實現的效率問題。對於業務自身的複雜性這類本質困難沒有幫助。

所以,業務上需要一些方法去對抗自身複雜性,以實現軟件的可複用性、可維護性和可擴展性。而設計模式正是一種被驗證過的有效方法。

當然,並不是所有業務都需要設計模式。如果業務複雜度在預期的時間段內是可控,可接受的,那麼過度的設計,反而會降低軟件的可維護性。

但是,在SaaS領域,業務複雜度的增速一般是非常快的,這跟SaaS軟件的特性不無關係。回想這幾年經歷的SaaS行業,SaaS軟件具備的區別於其他領域軟件的幾個明顯特性:

  • 行業領域的專業性
  • 商家場景的多樣性
  • 業務規則的不一致性
  • 個性需求的不確定性
  • 需求難以協調的剛性

這些特性,無一不在向我們表明着,SaaS軟件是非常注重可維護性,可擴展性,甚至更多。

業務上我們是非常需要設計模式的。

三、怎麼學習設計模式?

至此,我們已經探討了一部分意識上的問題,下面我們探討一些方法上的問題。如果追問工作中爲什麼沒有應用設計模式的經驗,歸結其原因,分爲這麼兩類:

  • 不會用,對設計模式不熟悉,不知道該怎麼應用。
  • 用不上,業務中一直找不到合適的場景,不知道用在哪。

其原因在於,對設計模式的學習方法以及認知上存在偏差。例如,不知道設計模式的核心關注點在哪。或者認爲設計模式都是割裂存在的,僅適用在單一場景下的解決方案的集合。

3.1 解構設計模式

設計模式的本質是經驗的總結。我們可以對設計模式進行抽象,更精確地表達它:

  • 名稱:模式名稱是幫助我們理解記憶,方便溝通交流的標識。它是場景的簡稱,場景描述了問題產生的背景。
  • 問題:它是場景中想要達成的目標與現狀之間的落差。通常一個模式中的問題,代表的是一類問題,不特指某一個具體的問題。
  • 方案:針對模式中的問題,存在已經被反覆實踐驗證過的最佳解決方案。解決方案並不描述一個特定而具體的設計或實現,具有一定普適性。

那麼你認爲最應該關注設計模式的哪一部分?

是名稱嗎? 每種設計模式的名字或者問題場景,你都非常清楚,但你可以熟練應用嗎?

是解決方案嗎? 解決方案固然重要,但是如果你不清楚方案要解決的問題,結果只能是拔劍四顧心茫然。

如果將設計模式的關注點放在問題場景(名稱)或者解決方案上,大腦就會驅動我們以場景爲觸發點,去匹配模式。

通常會出現以下問題:

  • 割裂看待各個模式,用熟悉的場景去套用模式
  • 有創建對象場景,立即會想到用工廠模式
  • 本地引用遠程服務場景,想到代理模式
  • String/Integer常量池,線程池,連接池,想到享元模式
  • 全部心思都在解決方案本身上,陷入細節不可自拔,各種方案越看越迷茫
  • 適配器、裝飾模式的區別
  • 策略、橋接模式也有相似之處

應用的場景數不勝數,場景背後的問題卻是殊途同歸。單個場景不難識別,但是實際業務開發中,往往是複合場景,識別難度大。這也是工作中難以應用設計模式的主要原因。

是問題嗎? 問題是模式存在的前提,從使用方的角度看,問題是模式的唯一使用標識。所以說問題纔是設計模式的核心。

模式基於問題提出,問題依託於場景存在。以問題爲出發點,去匹配模式。

一個場景中可能包含多個問題,以創建對象場景爲例。我們使用 問題=>模式的思路來分析下在這個過程中可能會遇到的問題:

  • 創建實例過程太複雜?
  • 爲什麼複雜?是依賴對象太多?有辦法解耦嗎?
  • 是對象本身屬性多?可以按需初始化嗎? [建造者模式]
  • 是要創建不同類型實例的邏輯太複雜?有辦法將創建行爲統一起來嗎? [工廠模式]
  • 創建實例成本太高?
  • 爲什麼太高?有優化空間嗎?複製對象替代創建新行爲可行嗎? [原型模式]
  • 延遲創建可以嗎?單例可行嗎? 實例共享可行嗎? [單例、享元模式]

3.2 小結

一連串問題下來,你會發現,設計模式是伴隨着問題的解決而被引入的(這裏隨着問題的細化確認,標出了幾個比較明顯的模式作爲示例),這遠比從場景套用模式來的更清晰自然。

總結起來就是,以具體場景爲切入點,以遇到的問題爲核心,匹配並組合模式,最終形成解決方案。

四、面向對象設計原則及共識

面向對象編程、設計原則、設計模式之間關係,恰似利劍、心法、劍法。手中有劍,心法爲綱,方能知劍招,悟劍意。

4.1 面向對象編程3大特性

封裝 / 繼承 / 多態是面向對象的3大基本特徵。我們從設計模式的角度,該怎麼去理解呢?

1.封裝 :本質目的是將類實現者與使用者分離,從類內部來看,只包含自己的屬性,儘量不依賴其他類,只暴露必要的行爲。我們經常提到的高內聚,低耦合是對它最佳的體現。耦合強度由高到低排序,泛化( is-a) = 實現( like-a) > 組合( part-a) > 聚合( contains-a) > 關聯( has-a) > 依賴( use-a)。

  • 泛化,比較好理解,是類與類之間或者接口與接口之間的繼承關係。
  • 實現,也比較常見,類與接口之間的關係。
  • 組合,是一種強關聯,強調部分是整體不可分割的一部分,具有相同的生命週期。例如手是身體的一部分。
  • 聚合,是一種組合稍弱的強關聯,強調個體相對於集體的獨立性,個體組成了集體,兩者生命週期是獨立的。例如獨立個體的人,聚在一起,形成了各種團體組織。
  • 關聯,強調的是擁有關係,是實際存在的邏輯關聯。例如夫妻雙方,互相爲對方的配偶,兩者之間關係爲關聯關係。 - 依賴,是一種耦合度較低的關係,這種關係一般是偶然性的、臨時性的。例如我們使用手機打遊戲,我們依賴了手機,本質上我們跟手機是不存在邏輯聯繫的。

2.繼承 :本質目的是抽象,是類與類之間的聯繫,表示的是 is-a的靜態關係。繼承同時也具備複用屬性與行爲的能力。

3.多態 :本質目的是複用,但是隻能複用行爲,表示的是 like-a的動態關係,在運行時體現出不同。

設計模式是基於對面向對象特性的充分理解,以及對類與對象之間相互關係的應用體現。

4.2 面向對象設計7大原則

設計實現一個系統時,我們一般先按功能劃分好模塊,以模塊中核心類爲起點,根據功能逐步向周邊延展設計其它類。

設計模式在這個過程中可以幫助我們進行高質量的代碼設計。但是模式是有限的,這些優秀的設計模式背後有沒有什麼通用的指導原則呢?

  1. 依賴倒置原則 :面向接口編程,不要針對實現編程。實現意味着應對變化的能力下降,儘量延遲到調用時再具體化。
  2. 開閉原則 :對擴展開放,對修改關閉。比較好理解,擴展新增引入的風險相對修改更可控一些。修改往往意味着,系統擴展性不夠。
  3. 里氏替換原則 :繼承父類的目的是爲了複用。高質量的繼承關係,是衍生類可以完全替換掉基類,並且系統的行爲不受到影響。如果子類不能完全替換父類,說明繼承是不徹底的,複用的目的就沒有達到。
  4. 單一職責原則 :一個類應該只承擔一個職責。承擔的職責過多,職責之間可能會相互耦合。這裏最難的就是劃分職責,職責必須恰如其分地表現實體的行爲。比如用戶賬號可以修改基礎信息,會員可以持有會員卡。如果不加以區分,只抽象一個用戶實體包括所有的行爲,顯然是不合適的。
  5. 接口隔離原則 :適度細化接口,接口的行爲儘量少。分治的思想,降低複雜性,系統更可控。
  6. 迪米特法則 :一個類對依賴的類知道的越少越好。本質目的是將複雜度控制在一定範圍內。
  7. 組合/聚合複用原則 :複用即可以通過繼承實現,也可以通過組合 / 聚合實現。區別在於,繼承表達 is-a的邏輯關聯,目的在描述結構,而不是複用。

五、總結

本文圍繞設計模式在業務中難以落地實踐的現象,總結了一些可能的原因。跟隨問題探討了設計模式的本質以及對設計模式存在的一些錯誤認知,給出了以應用設計模式爲目的,需要以問題爲核心的學習方式。最後講解了設計模式背後的通用原則及共識,幫助我們更好的理解設計模式。

本文轉載自公衆號有贊coder(ID:youzan_coder)。

原文鏈接

無用的設計模式(上篇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章