天天看點

高内聚,可靠軟體設計的支柱

作者:CSDN
高内聚,可靠軟體設計的支柱

【編者按】高内聚是軟體架構中經常被忽視的基石。本文将介紹高内聚的含義,重要性和實作高内聚的方法,如遵循單一職責原則、分解複雜的類、保持内聚的操作集和避免"上帝"對象等。

原文連結:https://www.codereliant.io/the-principle-of-high-cohesion-a-pillar-of-reliable-software-design/

未經允許,禁止轉載!

作者 | CodeReliant 社群 譯者 | 明明如月

責編 | 夏萌出品 | CSDN(ID:CSDNnews)

高内聚,可靠軟體設計的支柱

圖檔來源:Ross Sneddon / Unsplash

今天,我們要探讨軟體設計中一個核心原則:高内聚。這是軟體架構中一個常被忽視的基石,它能決定你的代碼的可靠性和效率。

高内聚,可靠軟體設計的支柱

了解内聚性

在我們學習高内聚的具體内容之前,首先必須了解軟體開發中内聚性的含義。簡單來說,内聚性是指系統中一個子產品、類或元件中的職責之間的緊密程度。當我們談論“高内聚”時,是指一個子產品或類擁有單一、明确定義的角色或職責的場景。

高内聚,可靠軟體設計的支柱

高内聚的重要性

高内聚不僅是一個理論概念,它在軟體設計中具有明确的、實際的好處:

  • 簡單性和可了解性: 當每個子產品隻有一個明确定義的功能時,它變得更簡單直覺。這種簡單性延伸到任何處理代碼的人,使其他開發者更容易維護和增強。
  • 更易維護和修改: 高内聚通常導緻較少的依賴性。較少的依賴性意味着系統中一個部分的變化不太可能影響其他部分,減少了錯誤的可能性,并簡化了修改。
  • 增加重用性: 當一個子產品被設計成隻有單一職責時,它就成為一個高度可重用的元件。你可以在應用程式的不同部分重用這個元件,甚至跨不同的項目。
  • 更好的測試和調試: 由于每個子產品可以被隔離地進行測試,測試變得更簡單。任何發現的錯誤也更容易跟蹤和修複,因為它們可能局限于代碼庫中一個特定的、明确定義的區域。
高内聚,可靠軟體設計的支柱

實作高内聚

實作高内聚并不總是簡單明了的。它需要仔細的設計決策。這裡提供一些參考原則,幫助你的代碼庫保持高度内聚:

單一職責原則 (SRP)

每個類或子產品應該隻有一個修改的原因。單一職責原則是 SOLID 設計原則之一(SOLID 是單一職責原則、開閉原則、裡式替換原則、接口隔離原則和依賴反轉原則的總稱),它規定一個類應該隻有一個職責。這可以作為維護高内聚的指導方針。

例如,假設我們在交易應用程式中有一個名為 TradeManager 的類,它目前負責下單和記錄交易活動。這個設計違反了單一職責原則(SRP),因為該類具有多個變更的原因。

它可能如下所示:

type TradeManager struct {              //...              }                  func (t *TradeManager) placeTrade(stockSymbol string, quantity int, tradeType string) {              // 下單邏輯              //...              }                  func (t *TradeManager) logTradeActivity(tradeActivity string) {              // 記錄交易活動邏輯              //...              }           

為了遵循 SRP 和實作高内聚,我們應該将這些職責分離到兩個不同的類中。一個類可以處理下單,另一個類可以處理記錄交易活動。

重構後的代碼如下所示:

type TradeManager struct {              //...              }                  func (t *TradeManager) placeTrade(stockSymbol string, quantity int, tradeType string) {              // 下單邏輯              //...              }                  type TradeActivityLogger struct {              //...              }                  func (l *TradeActivityLogger) logTradeActivity(tradeActivity string) {              // 記錄交易活動邏輯              //...              }           

在重構後的版本中,TradeManager 和 TradeActivityLogger 各自隻有一個職責,使代碼更具内聚性,也更易于維護。

分解複雜的類

如果發現一個類做了太多事情,可以将其分解成多個更易管理的類,每個類隻負責一個職責。這種分解将提高軟體的整體内聚性。

我們來看一個 OrderManager 類的示例,它管理訂單的所有方面,包括建立、驗證、執行、取消、列出和擷取訂單。

這個類顯然職責過多:

type OrderManager struct {              //...              }                  func (o *OrderManager) createOrder(stockSymbol string, quantity int, orderType string) {              // 建立新訂單邏輯              //...              }                  func (o *OrderManager) validateOrder(order Order) bool {              // 驗證訂單邏輯              //...              }                  func (o *OrderManager) executeOrder(order Order) {              // 執行訂單邏輯              //...              }                  func (o *OrderManager) cancelOrder(order Order) {              // 取消訂單邏輯              //...               }                  func (o *OrderManager) listOrders() []Order {              // 列出所有訂單邏輯              //...              }                  func (o *OrderManager) getOrder(orderId string) Order {              // 擷取特定訂單邏輯              //...              }           

這個類的職責過多,違反了單一職責原則。我們可以将職責分離到 OrderManager 和 OrderRepository 兩個類中。

OrderManager 類将負責與訂單生命周期直接相關的操作,如建立、驗證、執行和取消訂單。OrderRepository 将處理面向資料的操作,如列出和擷取特定訂單。

type OrderManager struct {              //...              }                  func (o *OrderManager) createOrder(stockSymbol string, quantity int, orderType string) {              // 建立新訂單邏輯              //...              }                  func (o *OrderManager) validateOrder(order Order) bool {              // 驗證訂單邏輯               //...              }                  func (o *OrderManager) executeOrder(order Order) {              // 執行訂單邏輯              //...              }                  func (o *OrderManager) cancelOrder(order Order) {              // 取消訂單邏輯              //...              }                  type OrderRepository struct {              //...              }                  func (r *OrderRepository) listOrders() []Order {              // 列出所有訂單邏輯              //...              }                   func (r *OrderRepository) getOrder(orderId string) Order {              // 擷取特定訂單邏輯              //...              }           

通過将職責分離到 OrderManager 和 OrderRepository 類中,設計現在更符合單一職責原則,提高了代碼的内聚性、可維護性和可讀性。每個類可以獨立開發、修改和測試,減少一個類的變更會不經意地影響另一個類的可能性。

保持操作集的内聚性

確定子產品或類中的操作形成一個内聚的集合。如果有不太适合的操作,請考慮将其移動到另一個更合适的子產品或建立一個新的子產品。

保持操作集的内聚性意味着給定子產品或類中的操作是緊密相關的,并有助于實作單一職責。以下是一個交易系統中StockTrade 類的示例:

type StockTrade struct {              stockSymbol string              quantity int               tradeType string              }                  func (s *StockTrade) setStockSymbol(stockSymbol string) {              s.stockSymbol = stockSymbol              }                  func (s *StockTrade) setQuantity(quantity int) {              s.quantity = quantity               }                  func (s *StockTrade) setTradeType(tradeType string) {              s.tradeType = tradeType              }                  func (s *StockTrade) getStockSymbol() string {              return s.stockSymbol              }                  func (s *StockTrade) getQuantity() int {              return s.quantity              }                  func (s *StockTrade) getTradeType() string {              return s.tradeType              }           

在上面的例子中,StockTrade 類維護了一個内聚的操作集。所有的 getter 和 setter 方法都與股票交易的屬性相關。

例如,如果我們在這個類中添加執行交易或記錄交易執行的方法,那将違反高内聚原則,因為執行和記錄是不同的職責,不屬于 StockTrade 類。相反,執行和記錄應該委托給專門設計用來處理這些其他目的的不其他類。

避免“上帝”對象

“上帝”對象是知道太多或做太多事情的對象。這些是低内聚的對象,維護起來很困難。将這樣的對象分解成更小、高度内聚的元件可以提高可了解性和可維護性。

我們來看一個交易應用程式中“上帝”接口的例子。我們将這個接口定義為 TradingSystem。它試圖做與交易相關的所有事情,從管理股票、交易、訂單到使用者帳戶等。

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               }           

因為它一次試圖做太多事情,是以該接口需要進行拆分。它不僅知道添加/删除/更新/列出股票,下單/取消/列出/擷取交易這樣的交易活動,還知道建立/删除/更新/列出/擷取使用者這樣的使用者管理活動。

這個接口可以分解成更具内聚性和可管理性的接口,每個接口處理一個職責,比如 StockManager、TradeManager、OrderManager 和 UserManager。這樣,每個接口及其實作類都更易于了解、維護和測試。

高内聚,可靠軟體設計的支柱

結論

高内聚是提高軟體健壯性和可維護性的經典指導原則。它通過提高代碼的整潔性、可維護性、可重用性和可測試性,幫助建構高可靠和高健壯的軟體。如果我們能夠多花一些時間確定子產品高度内聚,不僅可以提高你代碼庫的健康度,而且能在未來提高軟體可擴充性,確定它能夠被其他人輕松了解、測試和增強。

你還知道哪些提高代碼内聚性的原則和技巧,歡迎在評論區留言分享。