LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

為什么程序開發(fā)設(shè)計中單一職責(zé)原則好理解卻用不好?

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

經(jīng)典的設(shè)計原則有很多,其中包括:SOLID、KISS、YAGNI、DRY、LOD 等。

下面聊聊 SOLID 原則。SOLID 是面向?qū)ο笤O(shè)計和編程中的五個基本原則的首字母縮寫,由羅伯特·C·馬丁(Robert C. Martin)提出。這些原則旨在幫助開發(fā)人員創(chuàng)建易于維護(hù)和擴(kuò)展的軟件系統(tǒng)。下面是對這五個原則的詳細(xì)解釋:

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

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

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

開閉原則強(qiáng)調(diào)軟件實(shí)體(類、模塊、函數(shù)等)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。這意味著在設(shè)計一個模塊的時候,應(yīng)該使得這個模塊可以在不被修改的前提下進(jìn)行擴(kuò)展。這樣做可以減少因?yàn)樾薷默F(xiàn)有代碼而引入的錯誤,同時也使得系統(tǒng)更加靈活,易于添加新功能。

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

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

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

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

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

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

這些設(shè)計原則,從字面上理解都不難。一看就感覺懂了,但真的用到項(xiàng)目中的時候,會發(fā)現(xiàn),“看懂”和“會用”是兩回事,而“用好”更是難上加難。從我之前的工作經(jīng)歷來看,很多同事因?yàn)閷@些原則理解得不夠透徹,導(dǎo)致在使用的時候過于教條主義,拿原則當(dāng)真理,生搬硬套,反而適得其反。

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

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

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

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

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

這個原則其實(shí)很簡單:一個類就負(fù)責(zé)一個任務(wù)。就像我們不喜歡一個員工同時做太多不同的工作一樣,一個類也不應(yīng)該承擔(dān)太多功能。如果一個類做了太多不相關(guān)的工作,我們就得把它分成幾個小類,每個小類只負(fù)責(zé)一個具體的工作。

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

如何判斷類的職責(zé)是否足夠單一?

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

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

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; // 區(qū)  private String detailedAddress; // 詳細(xì)地址 // ...省略其他屬性和方法...}

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

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

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

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

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

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

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

  1. 類中的代碼行數(shù)、函數(shù)或?qū)傩赃^多,會影響代碼的可讀性和可維護(hù)性,我們就需要考慮對類進(jìn)行拆分;

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

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

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

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

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

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

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

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

類的職責(zé)是否設(shè)計得越單一越好?

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

/*** 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);    }}

如果我們想讓類的職責(zé)更加單一,我們對 Serialization 類進(jìn)一步拆分,拆分成一個只負(fù)責(zé)序列化工作的 Serializer 類和另一個只負(fù)責(zé)反序列化工作的 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);  }}

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

實(shí)際上,不管是應(yīng)用設(shè)計原則還是設(shè)計模式,最終的目的還是提高代碼的可讀性、可擴(kuò)展性、復(fù)用性、可維護(hù)性等。我們在考慮應(yīng)用某一個設(shè)計原則是否合理的時候,也可以以此作為最終的考量標(biāo)準(zhǔn)。

我們來一塊總結(jié)回顧一下。

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

一個類只負(fù)責(zé)完成一個職責(zé)或者功能。不要設(shè)計大而全的類,要設(shè)計粒度小、功能單一的類。單一職責(zé)原則是為了實(shí)現(xiàn)代碼高內(nèi)聚、低耦合,提高代碼的復(fù)用性、可讀性、可維護(hù)性。

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

不同的應(yīng)用場景、不同階段的需求背景、不同的業(yè)務(wù)層面,對同一個類的職責(zé)是否單一,可能會有不同的判定結(jié)果。實(shí)際上,一些側(cè)面的判斷指標(biāo)更具有指導(dǎo)意義和可執(zhí)行性,比如,出現(xiàn)下面這些情況就有可能說明這類的設(shè)計不滿足單一職責(zé)原則:

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

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

  • 私有方法過多;

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

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

3. 類的職責(zé)是否設(shè)計得越單一越好?

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


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

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲国产看片在线观看 | 日本在线一区二区三区欧美 | 亚洲国产欧美另类专区 | 中文字制服丝袜字幕在线 | 亚洲精品视频在线看 | 伊人久久综在合线亚洲2019人澡 |