高内聚,可靠软件设计的支柱

本文阐述了高内聚在软件设计中的重要性,介绍了如何通过单一职责原则、分解复杂类和避免‘上帝’对象来实现高内聚,以及其对代码简洁性、维护性和可扩展性的影响。

e057c5dbf6c600a33d1998958a761d92.gif

【编者按】高内聚是软件架构中经常被忽视的基石。本文将介绍高内聚的含义,重要性和实现高内聚的方法,如遵循单一职责原则、分解复杂的类、保持内聚的操作集和避免"上帝"对象等。

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

未经允许,禁止转载!

作者 | CodeReliant 社区       译者 | 明明如月

责编 | 夏萌

出品 | 优快云(ID:优快云news)

cd7b419f0f877cd9028bf674c4be8d3c.jpeg

图片来源:Ross Sneddon / Unsplash

今天,我们要探讨软件设计中一个核心原则:高内聚。这是软件架构中一个常被忽视的基石,它能决定你的代码的可靠性和效率。

b49d64d7babeac545f21aeec463a726f.png

理解内聚性

在我们学习高内聚的具体内容之前,首先必须理解软件开发中内聚性的含义。简单来说,内聚性是指系统中一个模块、类或组件中的职责之间的紧密程度。当我们谈论“高内聚”时,是指一个模块或类拥有单一、明确定义的角色或职责的场景。

f84c4229d7e05b945461501a0defb50d.png

高内聚的重要性

高内聚不仅是一个理论概念,它在软件设计中具有明确的、实际的好处:

  • 简单性和可理解性: 当每个模块只有一个明确定义的功能时,它变得更简单直观。这种简单性延伸到任何处理代码的人,使其他开发者更容易维护和增强。

  • 更易维护和修改:  高内聚通常导致较少的依赖性。较少的依赖性意味着系统中一个部分的变化不太可能影响其他部分,减少了错误的可能性,并简化了修改。

  • 增加重用性: 当一个模块被设计成只有单一职责时,它就成为一个高度可重用的组件。你可以在应用程序的不同部分重用这个组件,甚至跨不同的项目。

  • 更好的测试和调试: 由于每个模块可以被隔离地进行测试,测试变得更简单。任何发现的错误也更容易跟踪和修复,因为它们可能局限于代码库中一个特定的、明确定义的区域。

3278f43cf7d300b6050e9e36f0759c86.png

实现高内聚

实现高内聚并不总是简单明了的。它需要仔细的设计决策。这里提供一些参考原则,帮助你的代码库保持高度内聚:

单一职责原则 (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。这样,每个接口及其实现类都更易于理解、维护和测试。

537ca7a076199478ec8538c8c284f420.png

结论

高内聚是提高软件健壮性和可维护性的经典指导原则。它通过提高代码的整洁性、可维护性、可重用性和可测试性,帮助构建高可靠和高健壮的软件。如果我们能够多花一些时间确保模块高度内聚,不仅可以提高你代码库的健康度,而且能在未来提高软件可扩展性,确保它能够被其他人轻松理解、测试和增强。

你还知道哪些提高代码内聚性的原则和技巧,欢迎在评论区留言分享。

推荐阅读:

>>弱耦合提高软件可靠性设计

>>模块化是构建可靠软件的首要原则

>>构建可靠软件的核心要素有哪些?

1dc8c714de02c775edf54808082c032b.jpeg

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优快云资讯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值