設計模式學習筆記 - 設計原則 - 1.單一職責原則

前言

前面我們提到過 SOLID 原則,實際上 SOLID 由 5 個設計原則組成,分別是:單一職責原則、開閉原則、里氏替換原則、接口隔離原則和依賴反轉原則。它們分別對應 SLOID 中的 S、O、L、I、D 這 5 個英文字母。

今天來學習下 SOLID 原則中的第一個原則:單一職責原則。


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

單一職責原則 (Single Responsibility Principle),縮寫為 SRP。

英文原文的描述:A class or module should have a single responsibility。翻譯成中文就是,一個類或模塊只負責完成一個職責或功能。

注意,這個原則描述的對象包含兩個,一個是類,一個是模塊。不管是哪個對象,單一職責在應用到這兩個面對對象的時候,道理都是相通的。接下來,我會只從“類”設計的角度,來講解如何應用這個原則。

一個類只負責完成一個職責或者功能。也就是說,不要設計大而全的類,要設計粒度小、功能單一的類。要把大而全的類拆分成多個功能更加單一、粒度更細的類。

換個角度來說,一個類包含了兩個或者兩個以上業務不相干的功能,就不符合單一職責原則。

例如,一個類里面包含訂單的一些操作,又包含用戶的一些操作。而訂單和用戶是兩個獨立的業務領域模型,我們將兩個不相干的功能放到同一個類中,就違反了單一職責原則。為了滿足單一職責原則,需要將這個類拆分成兩個粒度更細、功能更單一的兩個類:訂單類和用戶類。

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

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

在一個社交產品中,用下面的 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 detailOfAddress; // 詳細地址// 省略其他屬性和方法...
}

對于這個問題,有兩種不同的觀點。

  • 一種觀點是,UserInfo 類包含的都是跟用戶相關的信息,所有的屬性和方法都隸屬于用戶這樣一個業務模型,滿足單一職責原則;
  • 另一種觀點是,地址信息在 UserInfo 類中,所占比重較高,可以繼續拆分成獨立的 UserAddressUserInfo 只保留出 UserAddress 之外的屬性,拆分之后的兩個類的職責更加單一。

實際上,要做出選擇,我們不能脫離具體的應用常見。如果,在這個社交產品中,用戶地址信息和其他信息一樣,只是單純地用于展示,那 UserInfo 現在的設計就是合理的。但是,如果這個社交產品發展地比較好,之后又在產品中添加了電商的模塊,用戶的地址信息還會用在電商物流中,那我們最好將地址信息從 UserInfo 中拆分出來,獨立成用戶物流信息。

再進一步延伸下,如果做這個社交產品的公司發展地越來越好,公司內部有開發出了很多其他產品。公司希望支持統一賬號系統,也就是用戶一個賬號可以在公司內的所有產品中登錄。這個時候,我們就需要對 UserInfo 進行拆分,將跟身份認證相關的信息(比如 emailtelephone 等)抽取成獨立的類。

從上面的例子,可以總結出,在不同的應用場景、不同階段的需求背景下,對同一個類的職責是否單一的判定,都是不一樣的。在某種應用場景或者當下的需求背景下,一個類的設計可能符合單一職責原則了,但是如果換個應用場景或者未來的某個需求背景下,可能就不滿足了,需要繼續拆分成粒度更細的類。

此外,從不同的業務層面去看待同一個類的設計,對類是否職責單一,也會有不同的認識。比如,例子用的 UserInfo 類。如果從“用戶”這個業務層面來看,UserInfo 包含的信息都屬于用戶,滿足單一職責原則。如果從更加細分的“用戶展示信息” “地址信息” “登錄認證信息”等等這些細細粒度的業務層面來看,那 UserInfo 就應該繼續拆分。

綜上所述,評價一個類的職責是否足夠單一,我們并沒有非常明確的、可以量化的標準。實際上,在真正的軟件開發中,也沒必要過于未雨綢繆,過度設計。所以,我們可以先寫一個粗粒度的類,滿足業務需求。隨著業務的發展,如果粗粒度的類越來越大,代碼越來越多,這個時候,我們就可以將這個粗粒度的類,拆分成幾個更細粒度的類。這個就是所謂的重構

這里還有些小技巧,能夠輔助你,從側面判斷一個類的職責是否足夠單一:

  • 類中的代碼行數、函數或屬性過多,會影響代碼的可讀性和可維護性,我們就需要考慮讀類進行拆分。
  • 類依賴的其他類過多,或者依賴的類的其他類過多,不符合高內聚、低耦合的設計思想,就需要考慮拆分。
  • 私有方法過多,就要考慮是否將私有方法獨立到新的類中,設置為 public 方法,供更多的類適用,從而提高代碼的復用性。
  • 比較難給類起合適的名字,很難用一個業務名詞概括,或者只能用一些籠統的 ManagerContext 之類的詞語來命名,這就說明類的職責定義得可能不夠清楚。
  • 類中的大量方法都是集中操作類中的某幾個屬性,比如,在 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(Map<String, String> object) {StringBuilder textBuilder = new StringBuilder();textBuilder.append(IDENTIFIER_STRING);textBuilder.append(gson.toJson(object));return textBuilder.toString();}public Map<String, String> deserialize(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(Map<String, String> object) {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 Map<String, String> deserialize(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.類的職責是否設計的越細越好?

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717106.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717106.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Libevent的使用及reactor模型

Libevent 是一個用C語言編寫的、輕量級的開源高性能事件通知庫&#xff0c;主要有以下幾個亮點&#xff1a;事件驅動&#xff08; event-driven&#xff09;&#xff0c;高性能;輕量級&#xff0c;專注于網絡&#xff0c;不如 ACE 那么臃腫龐大&#xff1b;源代碼相當精煉、易讀…

【Java】Java 中的方法引用寫法

概述 方法引用&#xff08;MethodReference&#xff09;是Lambda表達式的另一種格式&#xff0c;在某些場景下可以提高代碼的可讀性 使用條件 只可以替換單方法的Lambda表達式 什么意思呢 &#xff1f; 例如下面這個Lambda表達式就不可以使用方法引用替換&#xff0c;因為…

100243. 將元素分配到兩個數組中 I

說在前面 &#x1f388;不知道大家對于算法的學習是一個怎樣的心態呢&#xff1f;為了面試還是因為興趣&#xff1f;不管是出于什么原因&#xff0c;算法學習需要持續保持。 題目描述 給你一個下標從 1 開始、包含 不同 整數的數組 nums &#xff0c;數組長度為 n 。 你需要通…

C語言 快速排序——qsort函數的介紹

qsort函數 1. 函數介紹2. 函數使用2.1 整型排序2.2 字符排序2.3 字符串排序2.4 結構體排序 3. 用冒泡思想模擬qsort函數 我們以往使用冒泡排序和選擇排序等對數據進行排序時&#xff0c;有可能會遇到搞不清排序次數&#xff0c;運行時間過長等一些問題&#xff0c;并且這些排序…

aop監控spring cloud接口超時,并記錄到數據庫

引入pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0…

寶塔面板安裝各種組件以及部署應用服務

在linux服務器安裝寶塔面板 一、從寶塔官網下載exe安裝包&#xff0c;安裝命令從寶塔官網&#xff08;https://www.bt.cn/&#xff09;獲取 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh二、安…

自動駕駛加速落地,激光雷達放量可期(上)

1 激光雷達應用廣泛&#xff0c;汽車有望成最大催化 激光雷達&#xff08;LiDAR&#xff09;是一種主動遙感技術&#xff0c;通過測定傳感器發出的激光在傳感器與目標物體之間的傳播距離&#xff0c;來分析目標地物表面的反射能量大小、反射波譜的幅度、頻率和相位等信息&#…

Vue項目如何進行優化?

Vue項目優化 1.移除控制臺打印2.壓縮圖片3.CDN加速 1.移除控制臺打印 可以使用插件自動去除&#xff0c;插件包括babel-plugin-transform-remove-console、uglifyjs-webpack-plugin、terser-webpack-plugin。最后選擇了terser-webpack-plugin&#xff0c;腳手架vue-cli用這個插…

一文掃盲:訂單管理系統,訂單是公司生命線。

hello&#xff0c;我是貝格前端工場&#xff0c;本期給大家分享訂單管理系統的知識點&#xff0c;歡迎老鐵們點贊、關注&#xff0c;如有需求可以私信我們。 一、什么是訂單管理系統 單管理系統是一種用于管理和處理訂單的軟件系統。它通常用于企業、電子商務平臺、零售店等需…

高并發高可用--反向代理與負載均衡

高并發高可用架構是指能夠應對大量并發請求并保持高度可用的系統架構。為了實現這一目標&#xff0c;通常會采用一系列技術和策略&#xff0c;包括負載均衡、緩存、分布式系統、冗余部署、容錯處理等。 以下是一些構建高并發高可用架構的關鍵要點&#xff1a; 負載均衡&#…

GEE高階應用python wxee 和eemont——MODIS 中生成NDVI 數據的月度時序影像

結合 wxee 和 eemont eemont概述 谷歌地球引擎是一種基于云的服務,用于矢量和柵格數據的地理空間處理。地球引擎平臺擁有 JavaScript 和 Python API,可使用不同方法處理地理空間對象。谷歌地球引擎還提供了一個巨大的 PETABYTE 級柵格和矢量數據目錄,用戶可以在線處理這些…

技術小知識:面向對象和過程的區別 ⑤

一、思想區別 面相對象&#xff1a;始終把所有事情思考歸類、抽離封裝成對象來調用完成。 面向過程&#xff1a;直接平鋪展開按順序執行完成任務。 面向對象多了很多對象的創建、使用&#xff0c;銷毀的過程資源消耗。是一種模塊化編程思想。 https://www.cnblogs.com/kuangmen…

網絡爬蟲彈幕

1.分析網頁&#xff0c;獲取代碼&#xff0c;提取時間 想要提取出彈幕所在的節點&#xff0c;我們要使用 Beautiful Soup 解析模塊&#xff0c;需要從 bs4 中導入 BeautifulSoup 模塊 創建一個 BeautifulSoup 對象&#xff0c;傳入變量 xml 和解析器 lxml&#xff0c;將該對象賦…

Java自學day5

流程控制語句 流程控制語句:通過一些語句,控制程序的執行流程 順序結構 順序結構語句是Java程序默認的執行流程,按照代碼的先后順序,從上到下依次執行! package orderdemo;public class OrderDemo {public static void main(String[] args) {System.out.println("…

2.2 mul、div、and、or乘除指令及所有寄存器英文名

匯編語言 1. mul乘指令 兩個相乘的數&#xff0c;要么都是8位&#xff0c;要么都是16位 兩個8位數相乘 一個默認放在al中&#xff0c;另一個放在8位reg或內存字節單元中8位乘法&#xff0c;結果默認放在ax中例如&#xff1a;計算100*10 100和10小于255&#xff0c;可以做8位…

一(四)班課表

第二學期 課節時間星期一星期二星期三星期四星期五上午18:20-9:00數學數學數學京劇語文29:10-9:50勞動音樂語文語文音樂310:30-11:10語文語文美術道德與法治數學思維411:20-12:00科學輪滑美術體育英語下午513:20-14:00數學實踐活動音樂欣賞語文英語語文拓展614:10-14:50體育英語…

信息系統安全與對抗-作業2

目錄 1、使用自己姓名拼音創建一個賬戶&#xff0c; 并使用命令和圖形化查看 2、使用自己拼音打頭字母創建一個隱藏賬戶 &#xff0c;并使用命令和圖形化查看 3、使用命令啟動 telnet 服務 4、使用命令打開防火墻 23 端口 5、熟悉LINUX系統&#xff0c;使用命令行創建用戶…

Spring Cloud Nacos集成Seata2.0 AT模式

Spring Cloud Nacos集成Seata2.0 AT模式 以CentOS 7為例&#xff0c;介紹Spring Cloud Nacos集成Seata2.0 AT模式的流程。分成兩個步驟&#xff1a;1.安裝配置seata-server、2.項目集成seata-client 一、下載seata-server安裝包 根據自己的操作系統選擇要下載的安裝包格式&a…

2023年第十四屆藍橋杯大賽軟件類省賽C/C++大學A組真題

2023年第十四屆藍橋杯大賽軟件類省賽C/C大學A組部分真題和題解分享 文章目錄 藍橋杯2023年第十四屆省賽真題-平方差思路題解 藍橋杯2023年第十四屆省賽真題-更小的數思路題解 藍橋杯2023年第十四屆省賽真題-顏色平衡樹思路題解 藍橋杯2023年第十四屆省賽真題-買瓜思路題解 藍橋…

05-Linux部署MySQL

Linux部署MySQL 在今后的使用過程中&#xff0c;需要頻繁使用Linux系統&#xff0c;所以在Linux上安裝軟是必不可少的操作 。 前置要求 需要學習前四章知識&#xff0c;初識Linux、Linux基礎命令、Linux權限管理、Linux高階技巧這4個章節。需要開啟多態虛擬機&#xff0c;電…