高内聚,可靠软件设计的支柱
2024-06-23 0
高内聚,可靠软件工程的支柱
[编者注]高内聚性是软件架构中经常被忽视的基石。本文介绍了实现高内聚的意义、重要性和方法,例如遵循单一责任原则、分解复杂的类、保持一致的工作流程以及避免“神”对象。
原文链接:https://www.codereliant.io/the-principle-of-high-cohesion-a-pillar-of-reliable-software-design/
未经许可请勿转载!
作者|CodeReliant社区译者|明明如月
通讯编辑|.夏萌|出品。
照片来源:RossSneddon/Unsplash
今天我们将探讨软件工程的一个核心原则:高内聚。这是软件架构中经常被忽视的基石,它决定了代码的可靠性和效率。
了解凝聚力
在了解高内聚的细节之前,我们首先需要了解内聚在软件开发中意味着什么。简而言之,内聚性是指模块、类或系统组件内职责的紧密程度。当我们谈论“高内聚”时,我们指的是模块或类具有单一的、明确定义的角色或责任的场景。
高内聚力的重要性
高内聚不仅仅是一个理论概念,它在软件工程中具有明显的实际好处:
简单易懂:当每个模块只有一个明确定义的功能时,它就变得更简单、更直观。这种简单性延伸到了处理代码的每个人,使其他开发人员更容易维护和改进代码。
更容易维护和修改:高内聚通常会导致更少的依赖性。较少的依赖关系意味着系统某一部分的更改不太可能影响其他部分,从而减少了发生错误的可能性并简化了更改。
提高可重用性:当模块具有单一职责时,它就成为高度可重用的组件。您可以在应用程序的不同部分,甚至不同的项目中重用此组件。
更好的测试和调试:测试变得更简单,因为每个模块都可以单独测试。发现的任何错误也更容易跟踪和修复,因为它们很可能被定位到代码库中特定的、定义良好的区域。
实现高凝聚力
实现高凝聚力并不总是那么简单。它需要仔细的设计决策。以下是一些有助于保持代码库高度一致的指南:
单一职责原则(SRP)
每个类或模块只能有一个更改原因。单一职责原则是SOLID设计原则之一(SOLID是单一职责原则、开闭原则、内部替换原则、接口隔离原则和依赖倒置原则的统称)。应该只有一种责任。这可以作为保持高内聚力的指南。
例如,假设我们的交易应用程序中有一个TradeManager类,当前负责下订单和记录交易活动。此模型违反了单一职责原则(SRP),因为一个类有多个更改原因。
它可能看起来像这样:
typeTradeManagerstruct{//...}
func(t*TradeManager)placeTrade(stockSymbolstring,quantityint,tradeTypestring){//订单逻辑//...}
func(t*TradeManager)logTradeActivity(tradeActivitystring){//记录交易活动逻辑//...}
为了遵守SRP并实现高内聚性,我们应该将这些职责分为两个不同的类别。一个类可以处理下订单,另一类可以处理交易活动日志记录。
重构后的代码如下所示:
typeTradeManagerstruct{//...}
func(t*TradeManager)placeTrade(stockSymbolstring,quantityint,tradeTypestring){//订单逻辑//...}
typeTradeActivityLoggerstruct{//...}
func(l*TradeActivityLogger)logTradeActivity(tradeActivitystring){//记录交易活动逻辑//...}
在重构版本中,TradeManager和TradeActivityLogger只有一个职责,这使得代码更加统一,更易于维护。
分解复杂的类
如果你发现一个类做得太多了,你可以把它分成更多的部分轻松管理班级,每个班级只负责一项职责。这种分解提高了软件的整体凝聚力。
让我们看一个OrderManager类的示例,它管理订单的各个方面,包括订单创建、确认、履行、取消、列出和提货。
这个类显然有太多的责任:
typeOrderManagerstruct{//...}
func(o*OrderManager)createOrder(stockSymbolstring,quantityint,orderTypestring){//创建新的订单逻辑//...}
func(o*OrderManager)validateOrder(orderOrder)bool{//验证订单逻辑//...}
func(o*OrderManager)executeOrder(orderOrder){//执行订单逻辑//...}
func(o*OrderManager)cancelOrder(orderOrder){//取消订单逻辑//...}
func(o*OrderManager)listOrders()[]Order{//列出所有订单逻辑//...}
func(o*OrderManager)getOrder(orderIdstring)Order{//获取具体订单逻辑//...}
该类职责过多,违反了单一职责原则。我们可以将职责分为两个类,OrderManager和OrderRepository。
OrderManager类直接负责与订单生命周期相关的操作,例如创建、确认、执行和取消订单。OrderRepository处理数据驱动的操作,例如列出和检索特定订单。
typeOrderManagerstruct{//...}
func(o*OrderManager)createOrder(stockSymbolstring,quantityint,orderTypestring){//创建新的订单逻辑//...}
func(o*OrderManager)validateOrder(orderOrder)bool{//验证订单逻辑//...}
func(o*OrderManager)executeOrder(orderOrder){//执行订单逻辑//...}
func(o*OrderManager)cancelOrder(orderOrder){//取消订单逻辑//...}
typeOrderRepositorystruct{//...}
func(r*OrderRepository)listOrders()[]Order{//列出所有订单逻辑//...}
func(r*OrderRepository)getOrder(orderIdstring)Order{//获取具体订单逻辑//...}
通过将职责拆分为OrderManager和OrderRepository类,现在的设计更加符合单一职责原则,提高了代码的一致性、可维护性和可读性。每个类都可以独立开发、修改和测试,从而减少一个类中的更改意外影响另一个类的可能性。
保持动作顺序一致
确保模块或类的函数形成一个连贯的集合。如果某个功能不适合,请考虑将其移动到另一个更合适的模块或创建一个新模块。
保持一组操作一致意味着特定模块或类的操作密切相关并有助于实现单一职责。以下是交易系统中StockTrade类的示例:
typeStockTradestruct{stockSymbolstringquantityinttradeTypestring}
func(s*StockTrade)setStockSymbol(stockSymbolstring){s.stockSymbolstockSymbol}
func(s*StockTrade)setQuantity(quantityint){s.quantityquantity}
func(s*StockTrade)setTradeType(tradeTypestring){s.tradeTypetradeTy星期五}
func(s*StockTrade)getStockSymbol()string{returns.stockSymbol}
func(s*StockTrade)getQuantity()int{returns.quantity}
func(s*StockTrade)getTradeType()string{returns.tradeType}
在上面的示例中,StockTrade类维护一组函数。所有的getter和setter方法都与股票交易的属性有关。
例如,如果我们在此类中添加执行交易或记录交易执行的方法,则违反了高内聚原则,因为执行和记录是单独的职责,不属于StockTrade类。相反,执行和日志记录应该委托给专门设计用于处理这些其他目的的其他类。
避免“神”物品
“上帝”对象是一个知道太多或做太多事情的对象。这些是低内聚力的对象并且难以维护。将此类对象划分为更小、高度连贯的部分可以提高可理解性和可维护性。
让我们看一下交易应用程序中“上帝”界面的示例。我们将这个接口定义为TradingSystem。它尝试做与交易相关的一切,从库存管理、交易、订单到用户帐户等等。
类型交易系统
placeStore(storeStore)cancelStore(storeStore)listStore()[]StoreStore(idstring)Store
createOrder(orderOrder)validateOrder(orderOrder)executeOrder(orderOrder)cancelOrder(orderOrder)listOrders()[]OrderOrder(idstring)Order
createUser(userUser)deleteUser(userUser)updateUser(userUser)listUsers()[]UsergetUser(idstring)User}
因为它试图同时做太多事情,所以必须拆分界面。它不仅知道添加/删除/更新/上币、下单/取消/上币/获取交易等交易操作,还知道创建/删除/更新/上币/获取用户等用户管理操作。
这个用户界面可以分为更加统一和可管理的用户界面,每个用户界面处理自己的职责,例如StockManager、TradeManager、OrderManager和UserManager。这样,每个接口及其实现类就更容易理解、维护和测试。
压缩的
高内聚是提高软件耐用性和可维护性的经典指导原则。它通过提高代码清洁度、可维护性、可重用性和可测试性来帮助构建高度可靠和健壮的软件。如果我们能多花一点时间确保模块高度一致,我们不仅可以改善代码库的健康状况,而且还可以使软件在未来具有可扩展性,确保其他人可以轻松理解、测试和改进它。。
你还知道其他提高代码凝聚力的原理和技巧吗,在评论区留言分享吧?
本站文章均由用户上传或转载而来,该文章内容本站无法检测是否存在侵权,如果本文存在侵权,请联系邮箱:2287318951@qq.com告知,本站在7天内对其进行处理。