狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

為什么程序開發設計中單一職責原則好理解卻用不好?

admin
2024年4月19日 17:56 本文熱度 780

經典的設計原則有很多,其中包括:SOLID、KISS、YAGNI、DRY、LOD 等。

下面聊聊 SOLID 原則。SOLID 是面向對象設計和編程中的五個基本原則的首字母縮寫,由羅伯特·C·馬丁(Robert C. Martin)提出。這些原則旨在幫助開發人員創建易于維護和擴展的軟件系統。下面是對這五個原則的詳細解釋:

1. 單一職責原則(Single Responsibility Principle, SRP)

單一職責原則指出一個類應該只有一個原因引起變化,即一個類應該只負責一項職責。如果一個類承擔了過多的職責,那么在修改它以滿足一個職責的需求時,可能會產生副作用,從而影響到其他職責的功能。遵循單一職責原則可以使代碼更加清晰,降低類的復雜性,提高模塊化程度。

2. 開閉原則(Open/Closed Principle, OCP)

開閉原則強調軟件實體(類、模塊、函數等)應該對擴展開放,對修改關閉。這意味著在設計一個模塊的時候,應該使得這個模塊可以在不被修改的前提下進行擴展。這樣做可以減少因為修改現有代碼而引入的錯誤,同時也使得系統更加靈活,易于添加新功能。

3. 里氏替換原則(Liskov Substitution Principle, LSP)

里氏替換原則是指子類型必須能夠替換掉它們的基類型,即子類對象應該能夠替換掉父類對象被使用。這意味著在軟件中,子類繼承父類時,應該能夠保證父類的所有行為在子類中仍然有效。如果違反了這個原則,可能會導致在使用子類替換父類的情況下,程序出現錯誤或者異常。

4. 接口隔離原則(Interface Segregation Principle, ISP)

接口隔離原則主張接口應該小而專注,不應該強迫客戶程序依賴于它們不用的方法。這個原則的目的是降低類與接口之間的耦合度,使得類可以實現它們需要的接口,而不是實現一個龐大的、包含許多不必要方法的接口。這樣可以提高系統的靈活性和可維護性。

5. 依賴倒置原則(Dependency Inversion Principle, DIP)

依賴倒置原則是指高層模塊不應該依賴于低層模塊,兩者都應該依賴于抽象;抽象不應該依賴于細節,細節應該依賴于抽象。這個原則的核心思想是通過抽象來減少模塊間的耦合,使得系統更加模塊化,從而提高代碼的可讀性、可維護性和可擴展性。

這些設計原則,從字面上理解都不難。一看就感覺懂了,但真的用到項目中的時候,會發現,“看懂”和“會用”是兩回事,而“用好”更是難上加難。從我之前的工作經歷來看,很多同事因為對這些原則理解得不夠透徹,導致在使用的時候過于教條主義,拿原則當真理,生搬硬套,反而適得其反。

那么如何更好的理解這些原則呢?下面我通過一個例子來說明,力求使大家能夠不僅懂而且會用。

如何理解單一職責原則(SRP)?

單一職責原則的英文是 Single Responsibility Principle,縮寫為 SRP。這個原則的英文描述是這樣的:A class or module should have a single responsibility。如果我們把它翻譯成中文,那就是:一個類或者模塊只負責完成一個職責(或者功能)。

注意,這個原則描述的對象包含兩個,一個是類(class),一個是模塊(module)。關于這兩個概念,有兩種理解方式。一種理解是:把模塊看作比類更加抽象的概念,類也可以看作模塊。另一種理解是:把模塊看作比類更加粗粒度的代碼塊,模塊中包含多個類,多個類組成一個模塊。

無論哪種理解方式,想象一下,單一職責原則就像是給每個工作角色分配一項特定的任務。不管是哪種情況,這個原則都是一個道理:每個角色(或者說類)都應該只做一件事,而且要做好。現在,我們就聊聊在設計一個類的時候,怎么按照這個原則來操作。至于模塊怎么用這個原則,你可以自己想一想,原理是類似的。

這個原則其實很簡單:一個類就負責一個任務。就像我們不喜歡一個員工同時做太多不同的工作一樣,一個類也不應該承擔太多功能。如果一個類做了太多不相關的工作,我們就得把它分成幾個小類,每個小類只負責一個具體的工作。

比如說,你有一個類,它既處理訂單的事情,又處理用戶的事情。訂單和用戶是兩碼事,對吧?把這兩件事放在一個類里,就像讓一個人同時做廚師和會計的工作,這顯然是不合理的。按照單一職責原則,我們應該把這個類分成兩個:一個專門處理訂單的類,另一個專門處理用戶的類。這樣一來,每個類都只關注一件事情,工作起來就更加得心應手了。

如何判斷類的職責是否足夠單一?

從剛剛這個例子來看,單一職責原則看似不難應用。那是因為我舉的這個例子比較極端,一眼就能看出訂單和用戶毫不相干。但大部分情況下,類里的方法是歸為同一類功能,還是歸為不相關的兩類功能,并不是那么容易判定的。在真實的軟件開發中,對于一個類是否職責單一的判定,是很難拿捏的。我舉一個更加貼近實際的例子來給你解釋一下。

在一個社交產品中,我們用下面的 UserInfo 類來記錄用戶的信息。你覺得,UserInfo 類的設計是否滿足單一職責原則呢?

public class UserInfo {  private long userId;  private String username;  private String email;  private String telephone;  private long createTime;  private long lastLoginTime;  private String avatarUrl;  private String provinceOfAddress; // 省  private String cityOfAddress; // 市  private String regionOfAddress; // 區  private String detailedAddress; // 詳細地址 // ...省略其他屬性和方法...}

關于UserInfo這個類,大家看法可能不同。有人覺得,既然UserInfo里裝的都是關于用戶的各種信息,那么它就符合那個所謂的單一職責原則,意思就是一個類只干一種活兒。但另一些人認為,因為UserInfo里地址信息占了很大一部分,所以可以把這部分信息單獨拿出來,搞個新的UserAddress類,讓UserInfo只保留其他用戶信息。這樣一來,每個類負責的活兒就更專一了。

那哪種說法更靠譜呢?其實,這得看我們用這個社交軟件的具體情況。如果這個軟件就是用來展示用戶的基本信息,那現在的UserInfo設計就挺好。但如果這個軟件后來要加個購物功能,用戶的地址信息就得在物流中用到,那我們最好還是把地址信息單獨搞出來,弄成個專門的用戶物流信息類。

再往深了想,如果這個公司越做越大,又開發了一堆其他應用,還想讓所有應用都能用同一個賬號登錄,那我們就得再對UserInfo動動手腳,把跟登錄認證相關的信息,比如郵箱、手機號這些,再抽出來,單獨搞個類。

所以說,一個類要不要繼續拆,得看我們用它來干嘛,以及將來可能要干嘛。有時候,一個類現在看起來挺合適的,但換個環境或者將來需求變了,就可能不夠用了,得繼續拆。而且,從不同的角度看同一個類,也可能有不同的想法。比如,從“用戶”這個整體來看,UserInfo里的東西都跟用戶相關,看起來挺專一的。但如果我們從更細的角度看,比如“用戶展示信息”、“地址信息”、“登錄認證信息”,那我們可能就得繼續拆分UserInfo。

總的來說,判斷一個類是不是專一,這事兒挺主觀的,沒有絕對的標準。在實際編程時,我們也不用太著急,一開始就想得太完美。可以先弄個簡單的類,滿足現在的需要。等以后業務發展了,如果這個類變得越來越復雜,代碼一大堆,那時候再考慮把它拆成幾個小類。這個過程,其實就是我們常說的不斷改進和調整。

聽到這里,你可能會說,這個原則如此含糊不清、模棱兩可,到底該如何拿捏才好啊?

這里還有一些小技巧,能夠很好地幫你,從側面上判定一個類的職責是否夠單一。而且,個人覺得,下面這幾條判斷原則,比起很主觀地去思考類是否職責單一,要更有指導意義、更具有可執行性:

  1. 類中的代碼行數、函數或屬性過多,會影響代碼的可讀性和可維護性,我們就需要考慮對類進行拆分;

  2. 類依賴的其他類過多,或者依賴類的其他類過多,不符合高內聚、低耦合的設計思想,我們就需要考慮對類進行拆分;

  3. 私有方法過多,我們就要考慮能否將私有方法獨立到新的類中,設置為 public 方法,供更多的類使用,從而提高代碼的復用性;

  4. 比較難給類起一個合適名字,很難用一個業務名詞概括,或者只能用一些籠統的 Manager、Context 之類的詞語來命名,這就說明類的職責定義得可能不夠清晰;

  5. 類中大量的方法都是集中操作類中的某幾個屬性,比如,在 UserInfo 例子中,如果一半的方法都是在操作 address 信息,那就可以考慮將這幾個屬性和對應的方法拆分出來。

不過,你可能還會有這樣的疑問:在上面的判定原則中,我提到類中的代碼行數、函數或者屬性過多,就有可能不滿足單一職責原則。那多少行代碼才算是行數過多呢?多少個函數、屬性才稱得上過多呢?

比較初級的工程師經常會問這類問題。實際上,這個問題并不好定量地回答,就像你問大廚“放鹽少許”中的“少許”是多少,大廚也很難告訴你一個特別具體的量值。

如果繼續深究一下的話,你可能還會說,一些菜譜確實給出了,做某某菜需要放多少克鹽,放多少克油的具體量值啊。我想說的是,那是給家庭主婦用的,那不是給專業的大廚看的。類比一下做飯,如果你是沒有太多項目經驗的編程初學者,實際上,我也可以給你一個湊活能用、比較寬泛的、可量化的標準,那就是一個類的代碼行數最好不能超過 200 行,函數個數及屬性個數都最好不要超過 10 個。

實際上, 從另一個角度來看,當一個類的代碼,讀起來讓你頭大了,實現某個功能時不知道該用哪個函數了,想用哪個函數翻半天都找不到了,只用到一個小功能要引入整個類(類中包含很多無關此功能實現的函數)的時候,這就說明類的行數、函數、屬性過多了。實際上,代碼寫多了,在開發中慢慢“品嘗”,自然就知道什么是“放鹽少許”了,這就是所謂的“專業第六感”。

類的職責是否設計得越單一越好?

為了滿足單一職責原則,是不是把類拆得越細就越好呢?答案是否定的。我們還是通過一個例子來解釋一下。Serialization 類實現了一個簡單協議的序列化和反序列功能,具體代碼如下:

/*** Protocol format: identifier-string;{gson string}* For example: UEUEUE;{"a":"A","b":"B"}*/public class Serialization {    private static final String IDENTIFIER_STRING = "UEUEUE;";    private Gson gson;    public Serialization() {      this.gson = new Gson();    }
   
   public String serialize(Mapobject) {      StringBuilder textBuilder = new StringBuilder();      textBuilder.append(IDENTIFIER_STRING);      textBuilder.append(gson.toJson(object));      return textBuilder.toString();    }
   public Mapdeserialize(String text) {      if (!text.startsWith(IDENTIFIER_STRING)) {      return Collections.emptyMap();      }      String gsonStr = text.substring(IDENTIFIER_STRING.length());      return gson.fromJson(gsonStr, Map.class);    }}

如果我們想讓類的職責更加單一,我們對 Serialization 類進一步拆分,拆分成一個只負責序列化工作的 Serializer 類和另一個只負責反序列化工作的 Deserializer 類。拆分后的具體代碼如下所示:

public class Serializer {  private static final String IDENTIFIER_STRING = "UEUEUE;";  private Gson gson;  public Serializer() {     this.gson = new Gson();  }
 public String serialize(Mapobject) {      StringBuilder textBuilder = new StringBuilder();      textBuilder.append(IDENTIFIER_STRING);      textBuilder.append(gson.toJson(object));      return textBuilder.toString();  }}
public class Deserializer {  private static final String IDENTIFIER_STRING = "UEUEUE;";  private Gson gson;  public Deserializer() {     this.gson = new Gson();  }
 public Mapdeserialize(String text) {    if (!text.startsWith(IDENTIFIER_STRING)) {       return Collections.emptyMap();    }    String gsonStr = text.substring(IDENTIFIER_STRING.length());    return gson.fromJson(gsonStr, Map.class);  }}

雖然經過拆分之后,Serializer 類和 Deserializer 類的職責更加單一了,但也隨之帶來了新的問題。如果我們修改了協議的格式,數據標識從“UEUEUE”改為“DFDFDF”,或者序列化方式從 JSON 改為了 XML,那 Serializer 類和 Deserializer 類都需要做相應的修改,代碼的內聚性顯然沒有原來 Serialization 高了。而且,如果我們僅僅對 Serializer 類做了協議修改,而忘記了修改 Deserializer 類的代碼,那就會導致序列化、反序列化不匹配,程序運行出錯,也就是說,拆分之后,代碼的可維護性變差了。

實際上,不管是應用設計原則還是設計模式,最終的目的還是提高代碼的可讀性、可擴展性、復用性、可維護性等。我們在考慮應用某一個設計原則是否合理的時候,也可以以此作為最終的考量標準。

我們來一塊總結回顧一下。

1. 如何理解單一職責原則(SRP)?

一個類只負責完成一個職責或者功能。不要設計大而全的類,要設計粒度小、功能單一的類。單一職責原則是為了實現代碼高內聚、低耦合,提高代碼的復用性、可讀性、可維護性。

2. 如何判斷類的職責是否足夠單一?

不同的應用場景、不同階段的需求背景、不同的業務層面,對同一個類的職責是否單一,可能會有不同的判定結果。實際上,一些側面的判斷指標更具有指導意義和可執行性,比如,出現下面這些情況就有可能說明這類的設計不滿足單一職責原則:

  • 類中的代碼行數、函數或者屬性過多;

  • 類依賴的其他類過多,或者依賴類的其他類過多;

  • 私有方法過多;

  • 比較難給類起一個合適的名字;

  • 類中大量的方法都是集中操作類中的某幾個屬性。

3. 類的職責是否設計得越單一越好?

單一職責原則通過避免設計大而全的類,避免將不相關的功能耦合在一起,來提高類的內聚性。同時,類職責單一,類依賴的和被依賴的其他類也會變少,減少了代碼的耦合性,以此來實現代碼的高內聚、低耦合。但是,如果拆分得過細,實際上會適得其反,反倒會降低內聚性,也會影響代碼的可維護性。


該文章在 2024/4/19 18:01:13 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved