瑞士軍刀 VS 單一職責原則

轉自:http://www.cnblogs.com/ldp615/archive/2009/08/28/1555952.html

 

“單一職責原則”是面向對象軟件開發的基本原則之一,面向對象的思想又是從現實世界中總結出來。可最近發現面向對象的單一職責原則與現實好似有些衝突。

 我們先看下引起事非爭端的優雅的瑞士軍刀吧:

  

 瑞士軍刀都是多功能工具刀,上面左圖的至少有14項功能,右圖的至少有34項功能!

 

 

 這個是帶MP3播放功能的!還有更加強大的:
        

 這是知名瑞士刀製造商,威戈(Wenger)最新推出了一把長8.75英寸、重2磅(1公斤)內含87種工具的瑞士刀,編號16999。看來幾乎曾經出現在瑞士刀裏頭的東西都有了,它包括七種不同的小刀、高爾夫球鞋清理鉗、腳踏車鏈鉚釘調整器,還有一隻測距達300英尺(90公尺)的雷射筆。這把瑞士刀的作用,可能是讓一個當打完高爾夫球的人,能夠在清理完自己的球鞋以後,馬上把腳踏車給調校好,接着騎到一個人煙罕至的樹林裏。

 這個是相當“變態”的,我最近一直在寫擴展方法的文章,所有文章中的擴展方法合在一起也和軍刀有點類似了。

 瑞士軍刀和普通的刀(單一職責設計的刀)最大的不同就是方便(先不考慮最後一個,哈哈),它是多功能刀,而不是簡簡單單的刀。從功能上看,瑞士軍刀“打破”了單一職責原則。也許產品的設計和軟件系統的設計不能相提並論,畢竟歸規模、複雜度相差太遠。單一職責原則是爲解決複雜問題而制定的。那我們就換個思路去考慮,我們構建一支軍隊,現在要給士兵配一些裝備。軍隊、士兵、裝備全是對象!有點類似了吧。我們是給士兵配一把多功能的瑞士軍刀,還是配很多很多零散的小工具呢? 

 以上的對比可能不恰當,但從中可推出一個結論:有時使用單一職責,沒看到好的效果,反而更麻煩。

 先來看下單一職責原則(Single-Responsibility Principle)的定義,Robert C. Martin對它的解釋是:Each class should have one and only one reason to change. 單一職責並不是直觀理解爲類只能有單一的功能,單一職責是從變化的角度去考慮的。一個類應該(注意是should而不是must,是應該而不是必須)有且只有一個因起它變化的原因(直譯)。

 Martin的解釋改變的單一職責原則的含義:

 1.“單一職責”不是指具有單一功能(而是隻因一個原因變化);

 2.“原則”更像是一種比較強烈的建議。


 基於這兩點理解,引發出我的觀點:

 1.對象顆粒大小不同,對小顆粒對象應用單一職責是不合適的:小顆粒對象如小公司的員工,需要一人兼數職。如果應用單一職責,小顆粒對象又將分解成更多更小顆粒的對象,數量越來越多,給我們命名、使用、維護帶來麻煩。

 2.基礎類庫不適合:比如字符串常用處理的類,取子字符串、轉換爲其它類型、爲空判斷,放在一個類中反而使用更方便!

 3.“單一職責原則”更適於中大型系統,系統越小使用該原則越不划算:一超個小系統就一個界面,面象對象都不必使用。

 4.單一職責當然要用,但不是用到每個細節:一些局部不用對整體影響很小,反而更靈活、更省時間。 

 4.原則是前輩能多年的經驗總結出來的,原則的名字可能不變,但原則的內含可能在悄悄變化。

 5.“principle”、“原則”有可能是前人一種表達強烈建議的方式,而不是“must”、“必須”。

 

 再回頭看前面的軍刀,它有多種功能,但能引起它變化的原因是什麼呢?這裏想到一個,客戶應用。根據客戶的具體應用,會有多種功能組合,不同組合又分成不同系列...客戶有了新的需求,就會推出新產品...這個過程中可能包含了對象的繼承、組合等等,應該很複雜。當然也遇到客戶需求相當相當不一般,所以推出前面很變態的那款,應該算個特例吧!

 再考慮:軍刀上有一個開啤酒瓶用的啓子,如果啤酒瓶的蓋子大小變了呢?帶MP3功能的軍刀上有耳機插孔,耳機接口變了怎麼辦?仔細想想,能引起它變化的原因還真不少。但這些變化發生的可能性太小了,即便是真的發生了,軍刀該退役了,新品就推出了。 

 所以,我認爲瑞士軍刀還是比較符合單一職責原則的!前面推出的結論“有時使用單一職責,沒看到好的效果,反而更麻煩”,有兩種解釋: 

 1.將單一職責錯誤理解成了單一功能,又進一步去分解功能,所以麻煩;

 2.不分對象大小,不看程序規模,統統使用:用的不恰當,所以麻煩。 


 最後結論,面向對象的思想與原則與現實世界是一致的,並且與時俱進。

 本人沒有受過正規的軟件教育,對面向對象理解也是殘缺不全,對技術也是東一塊西一塊,雜亂的想法也很多。說的不對請各位前輩指正!

-------------------

思想火花,照亮世界

分類: c#
2
0
(請您對文章做出評價)
« 博主前一篇:c# 擴展方法奇思妙用變態篇四:string 的翻身革命
» 博主後一篇:c# 擴展方法奇思妙用高級篇四:對擴展進行分組管理
Add your comment

35 條回覆

  1. #1樓 資深那一年[未註冊用戶]2009-08-28 19:25
    先搶個沙發再評論
     回覆 引用   
  2. #2樓 資深那一年[未註冊用戶]2009-08-28 19:39
    先說一下瑞士軍刀它到底是什麼?是工具!所以如果要是一個工具類做成“瑞士軍刀”是沒問題的。因爲引起他變化的原因少,相對穩定。(當然功能也要限量 向第三幅圖的就不好了)OO 最根本的是要幹什麼?是要封裝變化,哪裏有可能出現變化就封裝哪裏。所以個人認爲單一原則 和 軍刀 基本是兩回事。
     回覆 引用   
  3. #3樓 丁丁      2009-08-28 19:48
    瑞刀三層的最實用,四層的已經太厚,對於特定用途,一層瑞刀的也沒啥不好。所以,看方便啦,寫代碼也一樣
     回覆 引用 查看   
  4. #4樓 Todd Wei      2009-08-28 20:09
    樓主選題很有啓發性,能引發人的思考本身就很不錯。
     回覆 引用 查看   
  5. #5樓 金色海洋(jyk)      2009-08-28 21:02
    我覺得用瑞士軍刀舉例子不大恰當。

    瑞士軍刀只不過是若干“單一職責的刀”的組合,只不過這種組合比較緊密。

    ============

    其實就是一句話:要分成小份,但是又不能太細。
     回覆 引用 查看   
  6. #6樓 zwws[未註冊用戶]2009-08-28 21:05
    引起瑞士軍刀變化的原因只用一個,那就是功能的增加。軍刀本身做爲一個個體,和它實際應用的環境是非耦合的,所以我覺得不應該和單一職責放在一起談,性質不同。

    比較贊同的說法是,單一職責的應該應用在粒度較大的點上,控制不好反而增加系統的複雜程度,得不償失。
     回覆 引用   
  7. #7樓 Ivony...      2009-08-28 21:46
    其實怎麼說呢,有句古話說的不錯,盡信書不如無書,很多東西不能從字面上去理解,不過這倒不是什麼大事,某些大牛也經常鬧出這種事情。就像前陣子我特意撰文反駁的什麼“類型要高內聚低耦合”。

    其實“單一職責原則”這個名字非常容易讓人誤解,而對它的解釋“只應有一個引起變化的原因”又顯得比較深奧,所以很多人是知其然不知其所以然。

    事實上單一職責與粒度沒有關係。簡單的說,.NET Framework有一個類型,SqlConnection,這個類型是負責建立和維護數據庫連接的,顯然其設計是符合單一職責原則的。我們現在假設自己做了一個類型,這個類型不但負責打開和維護數據庫連接,還要負責創建命令,執行查詢,幷包裝輸出結果等等一系列事情。但這個類並不是就違背了單一職責原則,它還是單一職責的,他負責與數據庫的交互。如果我們要對數據庫進行任何更改,需要修改這個類型的實現,但與數據庫無關的更改,就不需要更改它了。在這樣的例子裏,顯然後者的設計粒度比前者大得多。

    那麼什麼是違背單一職責的呢,比如說一個類型除了與數據庫交互,還要負責到前面去收集表單數據,這個時候我們就可以認爲這裏面有職責不明的問題,因爲至少有兩個引起它修改的原因,UI的變更或者數據庫的變更。
     回覆 引用 查看   
  8. #8樓 行萬里路      2009-08-29 00:40
    如果從面向對象的角度去分析瑞士軍刀, 其並不是見得是一個好的設計。導致它變化因素太多(可用功能、是否便於攜帶、是否可以隨意拆卸等)。但是爲什麼它在市場仍如此受到青睞,這其中可能還有其他原因。如品牌、本身多功能就是它賣點。
    如果讓我設計,我可能會把軍刀的Body設計成一個接口容器(就像.NET Windows Form模型中的IServiceContainer),而軍刀上那些功能我可能會將它們做容器中的服務提供者(ServiceProvider),他們實現了IServiceProvider這樣一個接口。只有實現了這個接口的功能纔可以裝上軍刀。大家可能知道了,他其實就是一個可拆卸、可插拔的多功能道具。
    (愚見)
     回覆 引用 查看   
  9. #9樓 飛林沙      2009-08-29 02:45
    其實我絕對單一職責究竟多單一,要看整個系統需求分析時所劃分的對象粒度而定。
    還是那句話,滿足需求的設計就是好設計。
     回覆 引用 查看   
  10. #10樓 Nick Wang (懶人王)      2009-08-29 04:52
    “只因一個原因變化”這個確實很晦澀,可以從另一個角度衡量,如果在兩個意圖不同的功能中都是用了一個類,而這個類的某些方法只在其中一個功能中使用,那麼可以說它不是職責單一的。但是意圖是否相同,取決於你自己對它的劃分,粒度可能很粗,也可能很細。

    1.對象顆粒大小不同,對小顆粒對象應用單一職責是不合適的:小顆粒對象如小公司的員工,需要一人兼數職。如果應用單一職責,小顆粒對象又將分解成更多更小顆粒的對象,數量越來越多,給我們命名、使用、維護帶來麻煩。

    --這個不認同,越小的對象越容易和應該職責單一,如果你所謂的小顆粒對象指的是功能(方法)較少或代碼行數較少的類。

    2.基礎類庫不適合:比如字符串常用處理的類,取子字符串、轉換爲其它類型、爲空判斷,放在一個類中反而使用更方便!
    --字符串的例子正好是職責單一啊,因爲它只管字符串,不管其它類型。

    3.“單一職責原則”更適於中大型系統,系統越小使用該原則越不划算:一超個小系統就一個界面,面象對象都不必使用。
    --跟系統規模大小無關,跟程序的壽命有關。一個程序寫出來不管規模大小,如果要生存多年,維護就是主要工作,系統設計的好壞直接決定維護成本。

    4.單一職責當然要用,但不是用到每個細節:一些局部不用對整體影響很小,反而更靈活、更省時間。
    --一個系統的設計思路不統一,怎麼讓人理解和維護,今天的靈活和省時間,變成明天的難以維護和費時間。

     回覆 引用 查看   
  11. #11樓[樓主] 鶴沖天      2009-08-29 08:24
    引用資深那一年:如果要是一個工具類做成“瑞士軍刀”是沒問題的。因爲引起他變化的原因少,相對穩定。(當然功能也要限量 向第三幅圖的就不好了)

    同意 資深那一年 的觀點!
     回覆 引用 查看   
  12. #12樓[樓主] 鶴沖天      2009-08-29 08:26
    引用金色海洋(jyk):其實就是一句話:要分成小份,但是又不能太細。
    同意,這是對“單一職責原則”最簡單、最通俗的理解!
     回覆 引用 查看   
  13. #13樓[樓主] 鶴沖天      2009-08-29 08:34
    引用Ivony...:簡單的說,.NET Framework有一個類型,SqlConnection,這個類型是負責建立和維護數據庫連接的,顯然其設計是符合單一職責原則的。我們現在假設自己做了一個類型,這個類型不但負責打開和維護數據庫連接,還要負責創建命令,執行查詢,幷包裝輸出結果等等一系列事情。但這個類並不是就違背了單一職責原則,它還是單一職責的,他負責與數據庫的交互。

    Ivony...的思想值得去好好思考!
     回覆 引用 查看   
  14. #14樓[樓主] 鶴沖天      2009-08-29 08:36
    引用行萬里路:如果讓我設計,我可能會把軍刀的Body設計成一個接口容器(就像.NET Windows Form模型中的IServiceContainer),而軍刀上那些功能我可能會將它們做容器中的服務提供者(ServiceProvider),他們實現了IServiceProvider這樣一個接口。只有實現了這個接口的功能纔可以裝上軍刀。大家可能知道了,他其實就是一個可拆卸、可插拔的多功能道具。

    想法很獨到!也期待有這種設計的軍刀出現!
     回覆 引用 查看   
  15. #15樓[樓主] 鶴沖天      2009-08-29 08:51
    引用Nick Wang (懶人王):
    1.對象顆粒大小不同,對小顆粒對象應用單一職責是不合適的...
    --這個不認同,越小的對象越容易和應該職責單一,如果你所謂的小顆粒對象指的是功能(方法)較少或代碼行數較少的類。
    ...

    功能(方法)少或代碼行數少的類,如果功能相近,不如合成一個工具類,就像我們把很多小工具放入工具箱中一樣。也和瑞士軍刀差不多,它集合了多個單一職責的小工具。

    引用Nick Wang (懶人王):2.基礎類庫不適合:比如字符串...
    --字符串的例子正好是職責單一啊,因爲它只管字符串,不管其它類型。

    我最近一直在寫擴展方法的相關文章,通過擴展,我賦予string很多功能,如IsMatch(正則判斷)、ToInt/ToDateTime(轉換爲其它類型)、ExecuteDOS/ExecuteSQL(執行DOS命令或SQL語句)。擴展後的string職責多了很多,這些擴展是否也單一職責呢?

    引用Nick Wang (懶人王):3.“單一職責原則”更適於中大型系統,系統越小使用該原則越不划算...
    --跟系統規模大小無關,跟程序的壽命有關。一個程序寫出來不管規模大小,如果要生存多年,維護就是主要工作,系統設計的好壞直接決定維護成本。

    我感覺系統越小越穩定,系統越小引起它變化的原因也越少。超小系統維護不是問題,大不了重寫一個。感覺超小系統中還要運行各種原則,是一種極端主義。

    引用Nick Wang (懶人王):單一職責當然要用,但不是用到每個細節...
    --一個系統的設計思路不統一,怎麼讓人理解和維護,今天的靈活和省時間,變成明天的難以維護和費時間。

    系統設計的思路應該是整體上的,一個類的設計應該側重接口或公有成員部分,至於內部實現則可以相對靈活一點。
     回覆 引用 查看   
  16. #16樓 heros      2009-08-29 08:57
    瑞士軍刀方便嗎,我並不覺得。也許我的系統中只需要一個指甲刀,但我不得不帶上一堆我用不着的元素。如果這一堆元素的數量並不大時,也許我可以忍受,但數量龐大時…………,就像最後那一把軍刀一樣,要我說就是垃圾一推。功能設計應該儘量的細粒度,必要的時候可以組合。行萬里路的想法很不錯,對外提供穩定的接口,內部功能可以靈活變換。正如strategy、facade等的意圖一樣。
     回覆 引用 查看   
  17. #17樓[樓主] 鶴沖天      2009-08-29 09:06
    引用heros:瑞士軍刀方便嗎,我並不覺得。也許我的系統中只需要一個指甲刀,但我不得不帶上一堆我用不着的元素。如果這一堆元素的數量並不大時,也許我可以忍受,但數量龐大時…………,就像最後那一把軍刀一樣,要我說就是垃圾一推。

    產品設計考慮特定的用戶需求,你只需要一個指甲刀,去買指甲刀好了,還便宜,重點是適用!最後一把軍刀我也很難接受,但存在就有道理,肯定有人喜歡這樣,也許是爲了收藏!

    引用heros:對外提供穩定的接口,內部功能可以靈活變換。正如strategy、facade等的意圖一樣

    我也贊同這個!
     回覆 引用 查看   
  18. #18樓 heros      2009-08-29 09:14
    產品可以包含n多的功能,但設計時要考慮細粒度。而不是在設計時就打造一個上帝。也許我的系統中只需要一個指甲刀,可能一會又需要一個小銼刀,需要指甲刀我給你一個指甲刀,需要小銼刀我給你一個小小銼刀,而不是一下扔一個瑞士軍刀,“喂,都在這,自己挑吧。”
    存在就有道理,但存在不是就正確。
     回覆 引用 查看   
  19. #19樓[樓主] 鶴沖天      2009-08-29 09:31
    @heros
    產品有n多功能,也會樣n多種型號,也會有n多種樣式,也會有n多公司生產...作爲消費者我們選擇的餘地非常多的!
    我現在有點感覺.Net Framwork就像是一個龐大的“軍刀”,裏面類異常多,類中的方法也很多,我們能用到的又能有多少,不還是從裏面挑嗎?
    產品可多樣化,但Framwork要考慮通用性,能滿足絕大多數人的需要。
     回覆 引用 查看   
  20. #20樓 heros      2009-08-29 09:35
    引用鶴沖天:
    @heros
    產品有n多功能,也會樣n多種型號,也會有n多種樣式,也會有n多公司生產...作爲消費者我們選擇的餘地非常多的!
    我現在有點感覺.Net Framwork就像是一個龐大的“軍刀”,裏面類異常多,類中的方法也很多,我們能用到的又能有多少,不還是從裏面挑嗎?
    產品可多樣化,但Framwork要考慮通用性,能滿足絕大多數人的需要。

    對不起,我們現在討論的是Simple Response Principle。n多的類不在討論範圍。
     回覆 引用 查看   
  21. #21樓 韋恩卑鄙      2009-08-29 09:54
    單一職責 在我來說 更像是對接口進行細分的標準

    比如

    IPerson 吃飯說話聽話微笑
    {eat() say () listen () smile() }

    IMale 站着尿尿 玩女人
    {StandingPee() , FuxkChicken()}

    而我實現了 Wayne :IPerson,IMale

    在我想要 FuxkChicken的時候 我不要
    Wayne.FuxkChicken()
    因爲方法太多了 我找不到
    我寧可
    ((IMail)Wayne).FuxkChicken() 點起來比較好找(當然我一般參數類型就是 IMale 所以也不會拆箱)

    這也是我建議你擴展方法分組的原因

    換個角度說 你圖中瑞士軍刀變態 但是真的是無順序存放的麼?它仍然是多個功能分組,分別實現的,設計師那能那麼不負責

    所以我強調只要按照單一職責原則進行分組 那麼大家也會欣然接受你的擴展實現
     回覆 引用 查看   
  22. #22樓[樓主] 鶴沖天      2009-08-29 10:12
    @韋恩卑鄙
    韋恩兄言語不羈,很豪放!
    IPerson、IMale讓我有種恍然大悟的感覺!
    對擴展進行分組的想法也在逐步形成中!
     回覆 引用 查看   
  23. #23樓[樓主] 鶴沖天      2009-08-29 10:16
    引用韋恩卑鄙:((IMale)Wayne).StandingPee()

    Wayne.As<IMale>.StandingPee()
    哈哈,我又多個一個思路:增加一個As<T>擴展,所有的擴展都擴展在T上,韋恩兄感覺如何?
    T最好是接口,再者As以A打頭,比較靠前,容易找也容易與其它的區分!
     回覆 引用 查看   
  24. #24樓 樂蜂網[未註冊用戶]2009-08-29 10:38
    寫的很好!
     回覆 引用   
  25. #25樓 Nick Wang (懶人王)      2009-08-29 11:33
    @鶴沖天
    1. 工具類本身的職責就是單一的啊,不過也可以細分成不同方面的工具,即使一個工具類有20多個方法,它也可能是SRP的。但如果其中包含了數據庫、字符串、html等等的功能,也可以說不是SRP的。
    2. 對字符串的擴展也還是字符串操作,當然也是SRP的。但是如果擴展中有針對數據庫的、針對測試的等等,還是分成多個擴展類比較清晰。
    --竊以爲工具類並不是OO的一部分,SRP與否也並不是太大的問題,主要還是從清晰和可讀性上考慮。

    3.這要看你如何定義系統的大小了。如果以功能來看,twitter的功能也很單一啊,但是並不意味着就不需要維護,不會有很長的生命週期。

    4.外部接口就像是穿的外衣,是給別人看的;內部實現就像內衣,舒服不舒服自己知道。而且外部接口一般是不能改變的或甚少改變的,內部實現則是會因爲很多原因而改變,所以內部實現也是很重要的。
     回覆 引用 查看   
  26. #26樓 陶發輝      2009-08-29 11:50
    瑞士軍刀=管理(組合(單一職責刀*N))
    但是它的前題是:
    1.每個刀都質量非常好,基本不會壞
    2.有足夠的空間
    3.有足夠的錢
    程序同理。
     回覆 引用 查看   
  27. #27樓 韋恩卑鄙      2009-08-29 12:44
    引用鶴沖天:
    引用韋恩卑鄙:((IMale)Wayne).StandingPee()

    Wayne.As<IMale>.StandingPee()
    哈哈,我又多個一個思路:增加一個As<T>擴展,所有的擴展都擴展在T上,韋恩兄感覺如何?
    T最好是接口,再者As以A打頭,比較靠前,容易找也容易與其它的區分!

    這個方法我很喜歡 搞得我也想試驗下了
     回覆 引用 查看   
  28. #28樓[樓主] 鶴沖天      2009-08-29 13:38
    引用Nick Wang (懶人王):竊以爲工具類並不是OO的一部分,SRP與否也並不是太大的問題,主要還是從清晰和可讀性上考慮。

    這個觀點(對我)很新穎!支持!
     回覆 引用 查看   
  29. #29樓 Sojin      2009-08-29 14:24
    我覺得某個東西是否符合某個原則要看你是怎麼去設計和理解這個東西。

    某種瑞士軍刀如果不可靈活拆卸某個小工具,那他只能看成一個整體,當然就不符合,如果某種瑞士軍刀是可以拆卸的,就可以將軍刀看成容器,他的職責就是組合這些工具。這樣軍刀類並不需要知道具體的工具,添加和減少工具也不會引起他結構的變化。

    在現實中可能雖然可拆卸的靈活,但成本更貴,用戶就會按自己的需求去購買東西,都會有他的市場。
    對應設計中有時候設計的成本高,又絕對沒有這種變化的需要,不符合某個職責又有什麼所謂呢。



     回覆 引用 查看   
  30. #30樓 Todd Wei      2009-08-29 15:22
    SRP指的是組件職責單一,瑞士軍刀不是組件,而是系統。沒有誰說系統要職責單一的,比如一套ERP系統,又是生產,又是財務,又是客戶,什麼都有。放到瑞士軍刀的例子裏面,瑞士軍刀系統是由多個xxx刀組件構成的,每個xxx刀的職責單一,這是SRP。系統功能多樣化和組件職責單一化並不矛盾。

    其實,所謂職責或功能是有抽象層次的,高層次的功能包含了若干低層次的功能。系統對應高的抽象層次,也可以認爲它是功能單一,比如:ERP系統,它唯一的功能就是企業資源計劃,不是搜索引擎。談論SRP應該放在對象所處的層次上。
     回覆 引用 查看   
  31. #31樓 heros      2009-08-29 17:31
    引用Todd Wei:

    "談論SRP應該放在對象所處的層次上"。上面我說的那麼多,也沒這句表達的清楚。SRP不能針對整個OA系統,它應該在設計員工類時被使用。
     回覆 引用 查看   
  32. #32樓 麒麟.NET      2009-08-29 22:42
    瑞士軍刀是一個產品,不是一個類,產品的功能自然會很多,而我們看到的是一個集合了多種功能的產品,功能多,職責複雜,也就不足爲怪了
     回覆 引用 查看   
  33. #33樓 軒轅健      2009-08-29 22:44
    我覺得,瑞士軍刀本身就已經是一個集成的系統,它並不是一個單一的整體對象。其組成就是由更多更小的功能實現體組合而成。
     回覆 引用 查看   
  34. #34樓 take it and go      2011-04-29 13:25
    瑞士軍刀其實有些功能對我們多餘了
     回覆 引用 查看   
  35. #35樓[樓主] 鶴沖天      2011-05-22 17:28
    @take it and go
    呵呵,是的
     回覆 引用 查看   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章