開發中關於設計的一些思考

設計準則

1 單一職責原則
單一原則很簡單,就是將一組相關性很高的函數、數據封裝到一個類中。換句話說,一個類應該有職責單一。

2 開閉原則
開閉原則理解起來也不復雜,就是一個類應該對於擴展是開放的,但是對於修改是封閉的。我們知道,在開放的app或者是系統中,經常需要升級、維護等,這就要對原來的代碼進行修改,可是修改時容易破壞原有的系統,甚至帶來一些新的難以發現的BUG。因此,我們在一開始編寫代碼時,就應該注意儘量通過擴展的方式實現新的功能,而不是通過修改已有的代碼實現。

3 里氏替換原則
里氏替換原則的定義爲:所有引用基類的地方必須能透明地使用其子類對象。定義看起來很抽象,其實,很容易理解,本質上就是說,要好好利用繼承和多態。簡單地說,就是以父類的形式聲明的變量(或形參),賦值爲任何繼承於這個父類的子類後不影響程序的執行。我們在抽象類設計之時就運用到了里氏替換原則。

4 依賴倒置原則
依賴倒置主要是實現解耦,使得高層次的模塊不依賴於低層次模塊的具體實現細節。怎麼去理解它呢,我們需要知道幾個關鍵點:

(1)高層模塊不應該依賴底層模塊(具體實現),二者都應該依賴其抽象(抽象類或接口)
(2)抽象不應該依賴細節
(3)細節應該依賴於抽象

其實,在我們用的Java語言中,抽象就是指接口或者抽象類,二者都是不能直接被實例化;細節就是實現類,實現接口或者繼承抽象類而產生的類,就是細節。使用Java語言描述就簡單了:就是各個模塊之間相互傳遞的參數聲明爲抽象類型,而不是聲明爲具體的實現類;

5 接口隔離原則
接口隔離原則定義:類之間的依賴關係應該建立在最小的接口上。其原則是將非常龐大的、臃腫的接口拆分成更小的更具體的接口。

6 迪米特原則
描述的原則:一個對象應該對其他的對象有最少的瞭解。什麼意思呢?就是說一個類應該對自己調用的類知道的最少。還是不懂?其實簡單來說:假設類A實現了某個功能,類B需要調用類A的去執行這個功能,那麼類A應該只暴露一個函數給類B,這個函數表示是實現這個功能的函數,而不是讓類A把實現這個功能的所有細分的函數暴露給B。

23設計模式

我對6大原則的總結:
1.單一職責 (高內聚)
2.接口隔離 (低耦合)
3.依賴倒置 (依賴抽象)
我認爲設計代碼時單單做到以上3點, 寫出來的代碼都不會太差。 但是還可能有一些其他的問題, 比如代碼複用性不高,業務對象間關係描述不夠清晰等。
所以基於6大原則, 前人將一些行之有效的java設計case整理成更加具現的23設計模式,如圖
在這裏插入圖片描述

設計模式與Java

這裏需要注意的是, 我們通常講的23種設計模式是和Java語言強相關的, 每種設計模式都會給出一或多種參考範式, 即與實現掛鉤,非6大模式那種純邏輯概念。 我認爲在學習揣摩設計模式的過程裏應該重意輕形。
這裏有個單例模式的case,java與kotlin餓漢式單例的寫法對比。
在這裏插入圖片描述
應該很難把 object xxx 這短短一行代碼稱作一種設計模式吧?( kotlin編譯器將其範式隱藏在編譯期,以下是decomplile字節碼得到的kotlin餓漢式範式)
在這裏插入圖片描述
再比如觀察者模式, 以下是Java中的設計範式。要在被觀察者內部去提供註冊觀察者的接口再提供出去,看起來還是有點囉嗦的。

在這裏插入圖片描述
而在c#中, 語言本身就有 委託&事件 的設計, 可以說這個模式c#本身就提供給開發者。http://www.runoob.com/csharp/csharp-event.html (再說一句題外話, 個人認爲c#在語言設計層面有許多亮點。比如委託, 這個設計本身比interface更輕量級, 更加符合"接口"這個定義, 如果開發者更傾向於使用委託而非接口,那麼就更可能會設計出符合"最少暴露"原則的代碼。)

所以一些固定的範式是可以用類似語法糖的方式隱藏或優化的, 在學習階段理解設計的思想更爲重要。一些說法比如"設計模式在一定程度上就是彌補語言的缺陷" 也是在說明這個問題。

設計模式重要嗎?

如果你是一個Java程序員, 設計模式範式就是你跟同事合作生產時的一種交流方式(前提是大家都理解範式),還是最高效的交流方式之一。 比如別人寫個observer那麼我大概就知道這個類向外暴露各種回調,或者寫一個adapter, 這就是在對接兩個接口。所以對待設計模式的正確方式,我認爲應該是學習時重意, 幹活時用形。
除了23設計模式,分層設計也可以達到同樣的效果,如mvc, mvp, mvvm 等。

但是設計模式本身就是完美的嗎? 它就沒有缺陷嗎? 當然不是。 前面也說了, 設計模式只是前人對一些行之有效的設計case的總結, 而設計模式的本質, 我認爲是"針對某些具體場景提供了一些效率較高的以代碼複雜度換靈活性的手段"。所以它的第一個問題在於代碼複雜度。

不要過度設計

很多時候我去看一些優秀開源庫的實現,會發現要在源碼流程裏面跳很久才能找到我想找的那個功能節點的實現,中間還會經過很多抽象接口類型的代碼跳轉,看的很費勁。
爲什麼會設計的這麼複雜呢?因爲作者爲那個功能節點提供了很多的靈活性/擴展性的設計。所以代碼複雜度高又會帶來額外的成本 (這個成本包括 學習api&理解作者的設計理念&可能帶來的額外調試成本&可能帶來的額外維護成本) 。
大部分的設計模式追求擴展性,也就是所謂的"依賴倒置"。那麼爲什麼軟件要追求擴展性呢? 我認爲你主要有兩點:

  1. 方便後續迭代,新的業務只需要按照規定好的接口類型實現即可, 不需要去更改以前的業務流程。 所以擴展性的設計也可以說是面向未來設計。那麼這就又帶來一個問題, 如果"未來沒來",會怎麼樣?
  2. 方便改代碼,(這裏的非狹義pm的需求變更,指功能的擴展方向變化) 結合了大量的設計模式,設計出n種接口類型,增加了n層的代碼複雜度, 最後只上了一個簡單的業務, 沒享受到設計模式的好處反受其害。
    這就是過度設計(過度設計本質上就是你爲可能發生的變動付出了過多的複雜度代價)。所以不要在業務前景不清晰的情況下過早的追求極致的靈活性, 更熟悉業務才能做出更加符合當前業務場景的設計。

小結

設計模式是好東西,但也只是好東西,不要一味的追求完美,實際上能快速完成當前的功能,又能很好的擴展的纔是一個好的設計方案。
代碼只是工具,設計模式只是實現方案的結構選型,好的產品規劃纔是最重要的。

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