1. 程式人生 > >深入淺出系列第一篇(設計模式之單一職責原則)—— 從純小白到Java開發的坎坷經歷

深入淺出系列第一篇(設計模式之單一職責原則)—— 從純小白到Java開發的坎坷經歷

各位看官大大們,晚上好。好久不見,我想死你們了...

 

 

先說說寫這個系列文章的背景:

工作了這麼久了,每天都忙著寫業務,好久沒有好好靜下心來好好總結總結了。正好這段時間公司組織設計模式的分享分,所以我才有機會在這裡和大家嘮嘮嗑。

也許因為自己是小白自學的吧,所以磕磕絆絆走了好多彎路。所以我深刻的理解到在自學的時候有一個前輩在前面引路是多麼重要。可以讓你少走很多彎路。

擁有更高的學習效率。特別是在一些問題上,苦思冥想很久都沒有結果,白白浪費了很多時間,也許有人點撥一下就能茅塞頓開。

有這麼一句話說的:如果你想真正掌握某個知識,那就嘗試把它教給別人吧。所以我苦思冥想了很久終於決定寫一個系列的文章,是為了幫助別人也是為了提升自己。

好了話不多說,下面咋們上乾貨。

 

說道設計模式呢,我相信很多人都學習過。但是設計模式到底是什麼呢?我相信每個人心中都有自己的答案,就像一千個人心中有一千個哈姆雷特。

那麼他到底是什麼呢?從字面意思來說,它就是程式碼設計時解決某些問題的套路。(哎...自古真情留不住唯有套路得人心啊。)

 

好了,看到這看官們就要問了,你這說的啥玩意,乾貨呢?盡在這扯玄學,這玩意到底應該怎麼學習?

各位看官莫慌,下面就帶大家進入設計模式的海洋。設計模式多不勝數,常見的設計模式就有23種(工作中我也只用到了代理模式,策略模式,單例模式,工廠模式和建造者模式)。

在學習他們之前,我們先要了解這些模式的設計思想,所謂知其然還要知其所以然。下面要講到的就是設計模式的六大原則之單一職責原則。

單一職責原則(Single Responsibility Principle)簡稱SRP:

定義:There should never be more than one reason for a class to change(原文解釋)

中文解釋:應該有且僅有一個原因引起類或者方法的變更

單一職責原則的好處:

  1. 類的複雜性降低,實現什麼職責都有清晰明確的定義。
  2. 複雜性降低也降低了出現問題的概率,並且提高了可讀性和可維護性。
  3. 變更引起的風險降低,變更是不可避免的,如果介面的單一職責做得好,一個介面的修改只對相應的實現類有影響,對其他的介面無影響,這對系統的擴充套件性、維護性都有非常大的幫助。

看到這的看官是不是準備開罵了。這他媽什麼玩意,講了半天,還是照本宣科。搞個錘子!

各位看官不要急,請往下看。

先請各位看一個類圖:

 各位看官大大有沒有發現什麼異常?

現在我來揭曉答案:

這是舉例的是一個我工作中遇到的反面例子,這個抽象類和介面很明顯的違反了單一職責原則。它包含了資料採集,解析,轉換,清洗,對映,計算,儲存等多種職責。

期初是為了方便開發,大家便絞盡腦汁封裝了一個公共處理類供大家使用。開始專案初期資料來源少,並沒有發生什麼問題,大家也很滿意。

但是(大家注意轉折來了)隨著專案的推進,資料來源、資料型別越來越多,隨之而來的資料對映方式也越來越多。

為了適應各種對映方式大家開始在公共類中加入一些可以覆寫的空方法,用子類繼承公共類覆寫某些方法來實現改變(這裡使用的是模板設計模式)。

這也導致這個公共基類被頻繁修改經常出現各種莫名其妙的bug,很多之前好的功能也會突然出現問題。而且後來方法越加越多,原先的方法也越來越長邏輯越來越難看懂,導致後面甚至都無法維護,

大家焦頭爛額,最後只能推到從來重新劃分職責細化方法。最後我們花費了大量的人力物力將之前的抽象類改造成了如下所示五個類:

AbstractDataCapture類:職責是資料採集 由它專門負責資料採集,該類包含主要抽象方法是資料採集方法(由具體子類自己實現)和一些輔助方法。

AbstractDataParser類:職責是資料解析,它專門負責資料解析,該類包含主要抽象方法是資料解析方法(由具體子類自己實現)和一些輔助方法。

AbstractDataCleansing類:職責是資料清洗,它專門負責資料轉換成對映model類和清洗資料,該類包含主要抽象方法是資料轉換方法和資料清洗方法(由具體子類自己實現)以及一些輔助方法。

AbstractDataHandler類:職責是資料處理,它專門負責資料處理,該類包含主要抽象方法是資料關係對映和資料的計算處理(由具體子類自己實現)以及一些輔助方法。

AbstractDataSave類:職責是資料持久化,該類包含主要方法是資料持久化方法(非抽象方法有公共實現)和一些輔助方法。

 

 這是我實際經歷的事情,這是一次血的教訓。

以下例子來自於《設計模式之禪》一書。推薦一讀

不知道各位看官有沒有看懂,沒看懂的話沒關係。我摘錄了一本書上的例子。

如果要你設計一個電話類,你會怎麼設計?是不是像下面這樣

public interface Phone {
    //撥通電話
    public void dial(String phoneNumber);
    //通話
    public void chat(Object o);
    //通話完畢 結束通話電話
    public void hangup();
}

 

 

 大家看看這個介面有沒有什麼問題?我相信大家都會說,這個沒什麼問題啊。我平時也是這麼寫的,某某框架的原始碼中也很多這樣的介面啊。

大家仔細看可以發現,這個介面不止一個職責。它包含了兩個職責:

  1. 協議管理(dial方法和hangup方法)負責撥號接通和掛機
  2. 資料傳輸(chat方法)負責把對方傳遞過來的訊號還原成聲音訊號

 我們可以這樣考慮問題,協議連線的變化會引起這個介面或者實現類的變化嗎?會的!那資料傳輸的變化會引起這個介面或實現類的變化嗎?

答案也是會的。那就簡單了,這裡有兩個原因都會引起介面或者實現類的變化。而且這兩個職責的變化不互相影響,我們可以考慮將其拆分成兩個介面。

 

 

 但是這個類圖雖然滿足單一職責原則,但是我相信看官們在設計的時候不會才採用這樣一種方式。一個手機要把ConnectionManager類和 DataTransfer類組合在一塊才能使用。

組合是一種強耦合關係,你和我都有共同的生命期,這樣的強耦合關係還不如使用類直接實現介面的方式,這樣還增加了類的複雜性,多了兩個類。於是我們決定在修改一下。

 

 這樣設計就比較完美的。一個類實現了兩個介面,把兩個職責融合在一個類中。你會覺得這個Phone有兩個原因引起變化啊,是的,但是別忘記了我們是面向介面程式設計,

我們對外公佈的是介面而不是實現類。而且如果要真的實現類的單一職責原則,就必須使用之前的組合模式。這樣會引起類見耦合過重,類的數量增加等為題,人為的增加了設計的複雜性。

 

總結:

大家看了之後是不是像反思一下了,我以前的設計是不是有問題了?不,並不是這樣的,不要懷疑自己。

原則本身是非常優秀的,但是現實有現實的難處,因為職責和變化因數難以被具體量化。

而且在日常開發中,我們還要考慮工期,成本,人員技術水平等等多種情況。原則是死的人事活的。

我們應該審視奪度,在適當的時候用適當的設計。我個人的意見是介面和方法設計一定要做到職責單一,類的設計儘量做到。

&n