【編者按】高內(nèi)聚是軟件架構(gòu)中經(jīng)常被忽視的基石。本文將介紹高內(nèi)聚的含義,重要性和實(shí)現(xiàn)高內(nèi)聚的方法,如遵循單一職責(zé)原則、分解復(fù)雜的類、保持內(nèi)聚的操作集和避免"上帝"對(duì)象等。
原文鏈接: /the-principle-of-high-cohesion-a-pillar-of-reliable-software-design/
(資料圖片僅供參考)
未經(jīng)允許,禁止轉(zhuǎn)載!
圖片來(lái)源: Ross Sneddon / Unsplash
今天, 我們要探討軟件設(shè)計(jì)中一個(gè)核心原則: 高內(nèi)聚 。這是軟件架構(gòu)中一個(gè)常被忽視的基石,它能決定你的代碼的可靠性和效率。
在我們學(xué)習(xí)高內(nèi)聚的具體內(nèi)容之前,首先必須理解軟件開(kāi)發(fā)中內(nèi)聚性的含義。簡(jiǎn)單來(lái)說(shuō),內(nèi)聚性是指系統(tǒng)中一個(gè)模塊、類或組件中的職責(zé)之間的緊密程度。當(dāng)我們談?wù)摗案邇?nèi)聚”時(shí),是指一個(gè)模塊或類擁有單一、明確定義的角色或職責(zé)的場(chǎng)景。
高內(nèi)聚不僅是一個(gè)理論概念,它在軟件設(shè)計(jì)中具有明確的、實(shí)際的好處:
簡(jiǎn)單性和可理解性 : 當(dāng)每個(gè)模塊只有一個(gè)明確定義的功能時(shí),它變得更簡(jiǎn)單直觀。 這種簡(jiǎn)單性延伸到任何處理代碼的人,使其他開(kāi)發(fā)者更容易維護(hù)和增強(qiáng)。
更易維護(hù)和修改 : 高內(nèi)聚通常導(dǎo)致較少的依賴性。 較少的依賴性意味著系統(tǒng)中一個(gè)部分的變化不太可能影響其他部分,減少了錯(cuò)誤的可能性,并簡(jiǎn)化了修改。
增加重用性 : 當(dāng)一個(gè)模塊被設(shè)計(jì)成只有單一職責(zé)時(shí),它就成為一個(gè)高度可重用的組件。 你可以在應(yīng)用程序的不同部分重用這個(gè)組件,甚至跨不同的項(xiàng)目。
更好的測(cè)試和調(diào)試 : 由于每個(gè)模塊可以被隔離地進(jìn)行測(cè)試,測(cè)試變得更簡(jiǎn)單。 任何發(fā)現(xiàn)的錯(cuò)誤也更容易跟蹤和修復(fù),因?yàn)樗鼈兛赡芫窒抻诖a庫(kù)中一個(gè)特定的、明確定義的區(qū)域。
實(shí)現(xiàn)高內(nèi)聚并不總是簡(jiǎn)單明了的。它需要仔細(xì)的設(shè)計(jì)決策。這里提供一些參考原則,幫助你的代碼庫(kù)保持高度內(nèi)聚:
每個(gè)類或模塊應(yīng)該只有一個(gè)修改的原因。單一職責(zé)原則是 SOLID 設(shè)計(jì)原則之一(SOLID 是單一職責(zé)原則、開(kāi)閉原則、里式替換原則、接口隔離原則和依賴反轉(zhuǎn)原則的總稱),它規(guī)定一個(gè)類應(yīng)該只有一個(gè)職責(zé)。這可以作為維護(hù)高內(nèi)聚的指導(dǎo)方針。
例如,假設(shè)我們?cè)诮灰讘?yīng)用程序中有一個(gè)名為 TradeManager 的類,它目前負(fù)責(zé)下單和記錄交易活動(dòng)。這個(gè)設(shè)計(jì)違反了單一職責(zé)原則(SRP),因?yàn)樵擃惥哂卸鄠€(gè)變更的原因。
它可能如下所示:
type TradeManager struct {
//...
}
func (t *TradeManager) placeTrade(stockSymbol string, quantity int, tradeType string) {
// 下單邏輯
//...
}
func (t *TradeManager) logTradeActivity(tradeActivity string) {
// 記錄交易活動(dòng)邏輯
//...
}
為了遵循 SRP 和實(shí)現(xiàn)高內(nèi)聚,我們應(yīng)該將這些職責(zé)分離到兩個(gè)不同的類中。一個(gè)類可以處理下單,另一個(gè)類可以處理記錄交易活動(dòng)。
重構(gòu)后的代碼如下所示:
type TradeManager struct {
//...
}
func (t *TradeManager) placeTrade(stockSymbol string, quantity int, tradeType string) {
// 下單邏輯
//...
}
type TradeActivityLogger struct {
//...
}
func (l *TradeActivityLogger) logTradeActivity(tradeActivity string) {
// 記錄交易活動(dòng)邏輯
//...
}
在重構(gòu)后的版本中,TradeManager 和 TradeActivityLogger 各自只有一個(gè)職責(zé),使代碼更具內(nèi)聚性,也更易于維護(hù)。
如果發(fā)現(xiàn)一個(gè)類做了太多事情,可以將其分解成多個(gè)更易管理的類,每個(gè)類只負(fù)責(zé)一個(gè)職責(zé)。這種分解將提高軟件的整體內(nèi)聚性。
我們來(lái)看一個(gè) OrderManager 類的示例,它管理訂單的所有方面,包括創(chuàng)建、驗(yàn)證、執(zhí)行、取消、列出和獲取訂單。
這個(gè)類顯然職責(zé)過(guò)多:
type OrderManager struct {
//...
}
func (o *OrderManager) createOrder(stockSymbol string, quantity int, orderType string) {
// 創(chuàng)建新訂單邏輯
//...
}
func (o *OrderManager) validateOrder(order Order) bool {
// 驗(yàn)證訂單邏輯
//...
}
func (o *OrderManager) executeOrder(order Order) {
// 執(zhí)行訂單邏輯
//...
}
func (o *OrderManager) cancelOrder(order Order) {
// 取消訂單邏輯
//...
}
func (o *OrderManager) listOrders() []Order {
// 列出所有訂單邏輯
//...
}
func (o *OrderManager) getOrder(orderId string) Order {
// 獲取特定訂單邏輯
//...
}
這個(gè)類的職責(zé)過(guò)多,違反了單一職責(zé)原則。我們可以將職責(zé)分離到 OrderManager 和 OrderRepository 兩個(gè)類中。
OrderManager 類將負(fù)責(zé)與訂單生命周期直接相關(guān)的操作,如創(chuàng)建、驗(yàn)證、執(zhí)行和取消訂單。 OrderRepository 將處理面向數(shù)據(jù)的操作,如列出和獲取特定訂單。
type OrderManager struct {
//...
}
func (o *OrderManager) createOrder(stockSymbol string, quantity int, orderType string) {
// 創(chuàng)建新訂單邏輯
//...
}
func (o *OrderManager) validateOrder(order Order) bool {
// 驗(yàn)證訂單邏輯
//...
}
func (o *OrderManager) executeOrder(order Order) {
// 執(zhí)行訂單邏輯
//...
}
func (o *OrderManager) cancelOrder(order Order) {
// 取消訂單邏輯
//...
}
type OrderRepository struct {
//...
}
func (r *OrderRepository) listOrders() []Order {
// 列出所有訂單邏輯
//...
}
func (r *OrderRepository) getOrder(orderId string) Order {
// 獲取特定訂單邏輯
//...
}
通過(guò)將職責(zé)分離到 OrderManager 和 OrderRepository 類中,設(shè)計(jì)現(xiàn)在更符合單一職責(zé)原則,提高了代碼的內(nèi)聚性、可維護(hù)性和可讀性。每個(gè)類可以獨(dú)立開(kāi)發(fā)、修改和測(cè)試,減少一個(gè)類的變更會(huì)不經(jīng)意地影響另一個(gè)類的可能性。
確保模塊或類中的操作形成一個(gè)內(nèi)聚的集合。如果有不太適合的操作,請(qǐng)考慮將其移動(dòng)到另一個(gè)更合適的模塊或創(chuàng)建一個(gè)新的模塊。
保持操作集的內(nèi)聚性意味著給定模塊或類中的操作是緊密相關(guān)的,并有助于實(shí)現(xiàn)單一職責(zé)。以下是一個(gè)交易系統(tǒng)中 StockTrade 類的示例:
type StockTrade struct {
stockSymbol string
quantity int
tradeType string
}
func (s *StockTrade) setStockSymbol(stockSymbol string) {
= stockSymbol
}
func (s *StockTrade) setQuantity(quantity int) {
= quantity
}
func (s *StockTrade) setTradeType(tradeType string) {
= tradeType
}
func (s *StockTrade) getStockSymbol() string {
return
}
func (s *StockTrade) getQuantity() int {
return
}
func (s *StockTrade) getTradeType() string {
return
}
在上面的例子中, StockTrade 類維護(hù)了一個(gè)內(nèi)聚的操作集。所有的 getter 和 setter 方法都與股票交易的屬性相關(guān)。
例如,如果我們?cè)谶@個(gè)類中添加執(zhí)行交易或記錄交易執(zhí)行的方法,那將違反高內(nèi)聚原則,因?yàn)閳?zhí)行和記錄是不同的職責(zé),不屬于 StockTrade 類。相反,執(zhí)行和記錄應(yīng)該委托給專門設(shè)計(jì)用來(lái)處理這些其他目的的不其他類。
“上帝”對(duì)象是知道太多或做太多事情的對(duì)象。這些是低內(nèi)聚的對(duì)象,維護(hù)起來(lái)很困難。將這樣的對(duì)象分解成更小、高度內(nèi)聚的組件可以提高可理解性和可維護(hù)性。
我們來(lái)看一個(gè)交易應(yīng)用程序中“上帝”接口的例子。我們將這個(gè)接口定義為 TradingSystem。它試圖做與交易相關(guān)的所有事情,從管理股票、交易、訂單到用戶帳戶等。
type TradingSystem interface {
addStock(stock Stock)
removeStock(stock Stock)
updateStock(stock Stock)
listStocks() []Stock
getStock(id string) Stock
placeTrade(trade Trade)
cancelTrade(trade Trade)
listTrades() []Trade
getTrade(id string) Trade
createOrder(order Order)
validateOrder(order Order)
executeOrder(order Order)
cancelOrder(order Order)
listOrders() []Order
getOrder(id string) Order
createUser(user User)
deleteUser(user User)
updateUser(user User)
listUsers() []User
getUser(id string) User
}
因?yàn)樗淮卧噲D做太多事情,所以該接口需要進(jìn)行拆分。它不僅知道添加/刪除/更新/列出股票,下單/取消/列出/獲取交易這樣的交易活動(dòng),還知道創(chuàng)建/刪除/更新/列出/獲取用戶這樣的用戶管理活動(dòng)。
這個(gè)接口可以分解成更具內(nèi)聚性和可管理性的接口,每個(gè)接口處理一個(gè)職責(zé),比如 StockManager 、 TradeManager 、 OrderManager 和 UserManager 。這樣,每個(gè)接口及其實(shí)現(xiàn)類都更易于理解、維護(hù)和測(cè)試。
高內(nèi)聚是提高軟件健壯性和可維護(hù)性的經(jīng)典指導(dǎo)原則。它通過(guò)提高代碼的整潔性、可維護(hù)性、可重用性和可測(cè)試性,幫助構(gòu)建高可靠和高健壯的軟件。如果我們能夠多花一些時(shí)間確保模塊高度內(nèi)聚,不僅可以提高你代碼庫(kù)的健康度,而且能在未來(lái)提高軟件可擴(kuò)展性,確保它能夠被其他人輕松理解、測(cè)試和增強(qiáng)。
你還知道哪些提高代碼內(nèi)聚性的原則和技巧,歡迎在評(píng)論區(qū)留言分享。
標(biāo)簽: