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