單一職責(zé)到底是什么?十分鐘帶你掌握!
在日常開發(fā)工作中,經(jīng)常會聽到有經(jīng)驗的技術(shù)念叨xxx需要注意單一職責(zé),那么,什么是單一職責(zé)?如何做才能保證職責(zé)單一?這篇文章幫你分析透。
什么是單一職責(zé)?
關(guān)于單一職責(zé),看過很多版本的解釋,這里歸納最常見的三個版本:
- 版本一:一個類只有一個引起變化的原因
- 版本二:一個類都應(yīng)該只負(fù)責(zé)一項職責(zé)
- 版本三:一個類只能干一件事情
哪個版本的解釋比較合理呢?
單一職責(zé)原則,英文是:Single responsibility principle(SRP),是 Robert C. Martin提出的 SOLID原則中的一種,所以,我們先看看 作者對單一職責(zé)原則的描述,這里摘取了作者關(guān)于單一職責(zé)的原文:
The Single Responsibility Principle (SRP) states that
each software module should have one and only one reason to change.
原文翻譯為:單一職責(zé)原則指出,任何一個軟件模塊都應(yīng)該有一個且只有一個修改的理由。
定義看起來很嚴(yán)謹(jǐn),但似乎和現(xiàn)實是相沖突的,因為軟件設(shè)計本身就是一門關(guān)注長期變化的學(xué)問,變化是軟件中最常見不過的問題,在現(xiàn)實環(huán)境中,軟件系統(tǒng)為了滿足用戶和所有者的要求,勢必會作出各種修改,而系統(tǒng)的用戶或者所有者就是該設(shè)計原則所指的"被修改的原因"。
于是乎,作者又重新把單一職責(zé)描述為:
The single responsibility principle states that every module
or class should have responsibility over a single part of
the functionality provided by the software, and that
responsibility should be entirely encapsulated by the class.
原文翻譯為:單一職責(zé)原則指出,每個模塊或類應(yīng)該只負(fù)責(zé)軟件所提供功能的一部分,并且這個職責(zé)應(yīng)該完全被該類封裝。
在這個定義中,每個模塊或者類只負(fù)責(zé)軟件的一部分功能,那這一部分是多少呢?這部分功能是否可以包含不同類型的行為呢?比如,電商中的訂單和物流都可以叫做電商的一部分功能,但是他們在業(yè)務(wù)意義上顯然是不同的領(lǐng)域,因此,該定義缺乏了定性。
于是乎,作者再次修改了單一職責(zé)的定義:
Each module should only be responsible to one actor.
原文翻譯為:任何一個軟件模塊都應(yīng)該只對某一類行為者負(fù)責(zé)
這個定義,只要是能歸結(jié)成一類的行為,都可以屬于某個模塊的功能,這樣定義看起來更符合現(xiàn)實業(yè)務(wù)的語意。
軟件模塊是什么?
在上述單一職責(zé)幾個定義中都提到了軟件模塊,那么,軟件模塊到底是什么呢?
軟件模塊(Software Module)是指軟件系統(tǒng)中的一個獨立單元,它包含一組相關(guān)的功能和數(shù)據(jù),這些模塊是通過封裝數(shù)據(jù)和功能來實現(xiàn)的,以便實現(xiàn)更高的代碼復(fù)用性、可維護(hù)性和可擴展性。通常具有以下特點:
- 獨立性:模塊是相對獨立的代碼單元,可以單獨開發(fā)、測試和部署。模塊的獨立性提高了系統(tǒng)的靈活性,使得各個模塊可以獨立演化和更新,而不影響其他模塊。
- 封裝性:模塊內(nèi)部的數(shù)據(jù)和實現(xiàn)細(xì)節(jié)對外界隱藏,只通過公開的接口與其他模塊進(jìn)行交互。封裝性提高了代碼的安全性和可維護(hù)性。
- 職責(zé)單一:每個模塊通常只負(fù)責(zé)一組相關(guān)的功能,這有助于遵循單一職責(zé)原則,使得模塊更加易于理解和維護(hù)。
- 可重用性:模塊設(shè)計得當(dāng),可以在不同的項目中重復(fù)使用,提高了開發(fā)效率和代碼質(zhì)量。
- 可替換性:模塊通過標(biāo)準(zhǔn)化的接口與外界交互,可以在不影響其他部分的前提下替換或更新某個模塊。
為了更好地說明軟件模塊,這里以一個電商系統(tǒng)為例,它可能包含以下幾個模塊:
(1) 用戶管理模塊:
- 功能:處理用戶的注冊、登錄、個人信息管理等。
- 接口:提供用戶注冊、登錄、信息更新等服務(wù)。
(2) 訂單管理模塊:
- 功能:處理訂單的創(chuàng)建、更新、查詢等。
- 接口:提供訂單創(chuàng)建、訂單狀態(tài)更新、訂單查詢等服務(wù)。
(3) 支付處理模塊:
- 功能:處理訂單的支付、退款等。
- 接口:提供支付請求、支付狀態(tài)查詢、退款等服務(wù)。
(4) 庫存管理模塊:
- 功能:處理商品的庫存查詢、更新等。
- 接口:提供庫存查詢、庫存更新等服務(wù)。
單一職責(zé)示例
為了更好的說明任何一個軟件模塊都應(yīng)該只對某一類行為者負(fù)責(zé)這個定義,下面我們通過2個 Java反例來進(jìn)行演示。
反例1
假設(shè)有一個 Employee員工類并且包含以下 3個方法:
public class Employee {
// calculatePay() 實現(xiàn)計算員工薪酬
public Money calculatePay();
// save() 將Employee對象管理的數(shù)據(jù)存儲到企業(yè)數(shù)據(jù)庫中
public void save();
// postEvent() 用于促銷活動發(fā)布
public void postEvent();
}
剛看上去,這個類設(shè)計得還挺符合實際業(yè)務(wù),員工有計算薪酬、保存數(shù)據(jù)、發(fā)布促銷等行為,但是這 3個方法對應(yīng)三類不同的行為者,計算薪酬屬于財務(wù)的行為,保存數(shù)據(jù)屬于數(shù)據(jù)管理員的行為,發(fā)布促銷屬于銷售的行為。
因此,Employee類將三類行為耦合在一起,違反了單一職責(zé)原則。假如一個普通員工不小心調(diào)用了calculatePay()方法,把每個員工的薪酬計算成了實際工資的2位,那可想而知這是一個災(zāi)難性的問題。
如果增加新需求,要求員工能夠?qū)С鰣蟊?,因此,需要?Employee類中增加了一個新的方法,代碼如下:
// 導(dǎo)出報表
void exportReport();
接著需求又一個一個增加,Employee類就得一次一次的變動,這會導(dǎo)致什么結(jié)果呢?
一方面,Employee類會不斷地膨脹;另一方面,可能業(yè)務(wù)需求完全不同,卻始終需要在同一個 Employee類上改動,合理嗎?
聯(lián)想一下你的日常開發(fā),是否也有這樣的設(shè)計?把很多不同的行為都耦合到同一個類中,然后隨著業(yè)務(wù)的增加,該類急劇膨脹,最后無法維護(hù)。
該如何解決這種問題呢?
解決這個問題的方法有很多,特定的行為只能由特定的行為者來操作,因此,需要把 Employee類拆解成 3種行為者(財務(wù)、數(shù)據(jù)管理員、銷售),Employee類拆分之后的代碼如下:
// 財務(wù)行為
public class FinanceStaff {
public Money calculatePay();
}
// 數(shù)據(jù)管理員行為
public class TechnicalStaff {
public void save();
}
// 銷售行為
public class OperatorStaff {
public String postEvent();
}
反例2
假設(shè)需要開發(fā)一個電商系統(tǒng),其中有一個 Order訂單類,負(fù)責(zé)處理訂單的創(chuàng)建、訂單的支付以及訂單的通知,代碼如下:
public class Order {
public void createOrder() {
// 訂單創(chuàng)建邏輯
}
public void processPayment() {
// 支付處理邏輯
}
public void sendNotification() {
// 發(fā)送通知邏輯
}
}
在上述代碼中,Order類同時承擔(dān)了訂單創(chuàng)建、支付處理和通知發(fā)送的職責(zé),違反了單一職責(zé)原則,因為一個類有多個引起變化的原因。
為了遵循SRP,我們需要將不同的職責(zé)分離到不同的類中,因此可以創(chuàng)建三個類:Order類負(fù)責(zé)訂單創(chuàng)建,PaymentProcessor類負(fù)責(zé)支付處理,NotificationService類負(fù)責(zé)通知發(fā)送,每個類都只承擔(dān)一個職責(zé),從而遵循了單一職責(zé)原則。代碼如下:
public class Order {
public void createOrder() {
// 訂單創(chuàng)建邏輯
}
}
public class PaymentProcessor {
public void processPayment(Order order) {
// 支付處理邏輯
}
}
public class NotificationService {
public void sendNotification(Order order) {
// 發(fā)送通知邏輯
}
}
上面2個示例代碼的拆分都遵從了原則:因相同原因而發(fā)生變化的事物聚集在一起,因不同原因而改變的事物分開。這就是單一職責(zé)的真正體現(xiàn),也是定義內(nèi)聚和耦合的一種方式。
總結(jié)
從作者 Robert C. Martin對單一職責(zé)的 3次定義變更,我們可以看出:
- 單一職責(zé)原則本質(zhì)上就是要理解分離關(guān)注點。
- 單一職責(zé)原則可以應(yīng)用于不同的層次,小到一個函數(shù),大到一個系統(tǒng)。
- 軟件設(shè)計也不可能一成不變。
回歸到實際的工作中,我們可以把一個系統(tǒng)模塊看作一個單一職責(zé)的行為者,比如:訂單系統(tǒng)只關(guān)注訂單相關(guān)的行為,交易系統(tǒng)只關(guān)注交易相關(guān)的行為,我們也可以把類作為一個單一職責(zé)的行為者,比如:訂單類,把訂單相關(guān)的 CRUD聚合在一起,支付類,把支付相關(guān)的信息聚合在一起。
因此,任何一個軟件模塊都應(yīng)該只對某一類行為者負(fù)責(zé)這個定義才更適合單一職責(zé)。
最后,單一職責(zé)原則是面向?qū)ο笤O(shè)計的重要原則之一,它可以提高代碼的可維護(hù)性、可讀性和可擴展性,在日常開發(fā)中,遵循 SRP可以有效地降低類之間的耦合度,提高系統(tǒng)的穩(wěn)定性和靈活性,從而寫出更高質(zhì)量的代碼。