前言在当今不断发展的世界中,客户需求以前所未有的速度不断变化。软件团队必须适应新需求并迅速交付更改。为此,减少软件开发和测试时间很有必要。同时,每隔一年都会推出新技术。通过替换现有技术来试验更优化、更高效的技术是很常见的。因此,编写的代码高内聚且松耦合十分重要。 好在著名大师Robert Martin(Bob 大师)提出的SOLID软件设计原则,给了我们正确的指导,我们将逐步介绍这五项原则,并为每一项原则进行说明。 S—单一职责原则这是最容易理解的原则之一。它指出“一个类必须只有一个改变的理由”。很多时候,您可能会发现一个类执行的功能多于它应该执行的功能。 假设您正在为银行软件编写代码。功能是显示给定用户的声明。该代码从数据库中获取数据并以用户选择的格式显示数据,看看下面的代码有什么问题。 银行报表管理器 从上面的代码片段中可以看出,BankStatementMgr类同时执行多项操作。它从数据库中获取数据,解析结果,然后以用户指定的格式显示它。可以发现它存在以下的几个问题:
可以通过以下方法解决上面的问题:
以下是我们修改后的代码: 银行报表管理器 语句格式器 事务DAO 所以每次在一个类中添加一个方法、或者一个方法中添加代码逻辑的时候,问问自己这真的属于这里吗?这是我家的“孩子”吗? O—开闭原则该原则指出代码应该对扩展开放,对修改关闭。如果需要添加新功能,则必须扩展该类。此外,为了使系统具有可扩展性,它的行为应该被隔离。 我们将通过一个例子来理解这一点。假设您是电子商务商户,通过不同的方式接受付款。您整合了Paypal、Wepay、Google Pay等不同模式,开发了支付处理器。你写出了下面的代码: 支付处理器 付款处理程序 处理付款请求的PaymentHandler 。PaymentProcessor确定模式并将其委托给正确的操作。此代码违反了开闭原则,因为任何功能都需要在PaymentProcessor和PaymentHandler 中进行修改。这种设计是不可扩展的,因为每一种新的支付方式都会在 switch 语句中引入一个新的 case 块。 为了使代码可扩展,我们可以使PaymentHandler抽象并定义一个方法来处理付款。为了处理新的支付模式,我们可以扩展这个基类并覆盖它的 handlePayment 方法。下面是新代码。 抽象支付处理程序 谷歌支付处理程序 CardPaymentHandler 我们现在将创建一个工厂类,它将负责存储特定的处理程序并根据模式返回它。 支付处理器工厂 支付处理器 我们的新代码现在符合开闭原则。要添加新的行为,我们只需要扩展我们的抽象类PaymentHandler并在工厂中配置它。无需修改 PaymentProcessor。 L—里式替换原则乍一看,这个名字听起来很吓人。该原则指出,同一超类的对象应该能够在不破坏现有代码的情况下相互替换。 我们将以开发电影剪贴板为例。scrapper 提供了一个通过电影名称或演员搜索电影的界面。 电影搜索 电影数据库搜索 烂番茄搜索 使用 MovieSearch 接口的客户端代码 我们有两种不同的实现。一个用于烂番茄,另一个用于 IMDB。它们都是可替换的,并且可以使用相同的界面进行访问。 如果未实现派生类中的方法,则违反了该原则。下面是一个违反里氏原则的例子。 所有电影搜索 在这种情况下,我们不能用 All Movies 替换其他派生类,例如 IMDB 和 烂番茄。方法searchByMovieName不是由它实现的,并且不会导致客户端代码中的一致行为。 I—接口隔离原则根据这个原则,客户端不应该实现它不需要的方法。如果您定义客户端不使用的方法,接口会变得过于笨重和受到污染。 如果一个界面由于混合功能而变得太大,将它分成多个较小的界面是有意义的。让我们看一个投资组合服务示例,该服务允许客户订购股票、ETF、期权等。 接口组合 我们已经定义了一个接口Portfolio,它允许客户订购股票、ETF 以及两者的组合。 ETF订单服务 股票订单服务 我们有两种不同的投资组合服务实现。StockOrderService尚未实现orderETF和orderStockAndETFs方法。ETFOrderService仅仅实现orderETF。 如果我们决定在订购股票时添加价格作为参数怎么办?它需要更改orderStocks方法以接受价格作为参数。此外,此更改必须由ETFOrderService 合并,即使它不支持orderStocks方法。 为了克服这个问题,我们可以将接口分为两个StockPortfolio,ETFPortfolio。 股票投资组合 ETF投资组合 使用新接口,StockOrderService 不需要处理订购 ETF。这同样适用于 ETFOrderService。 ETF订单服务 股票订单服务 接口隔离与单一职责和里氏替换原则有一些相似之处。 在上面带有庞大接口的示例中,我们在 StockOrderService 中抛出了一个异常。这违反了里氏替换原则。在这种情况下,派生类不会扩展功能。 如果在接口中定义了不相关的方法,那么该类将有多种更改原因。这违反了单一职责原则。 D—依赖倒置根据依赖倒置,程序中的高层模块不能与低层模块紧密耦合。两个模块都必须依赖于抽象。该原则提供了一种构建松耦合软件模块的机制。 让我们看看下面的例子。在此示例中,类OrderHistory从 PostgreSQL 数据存储中获取数据。 订单历史 OrderHistory类必须知道 PostgresDB 依赖项的实现细节。如果我们决定使用不同的数据库驱动程序,我们将需要用新的依赖项替换所有PostgresDB实例。 此外,数据库驱动程序更改的功能之一是什么?它还需要更改调用数据库驱动程序方法的OrderHistory类。 可以通过声明接口DataStore来消除这种耦合。该接口将公开消费者将调用的 API。我们可以有多个DataStore实现— Postgres DataStore 、MySQL DataStore 等 数据存储 Postgres数据存储 订单历史 我们的消费者类现在不必处理正在使用的数据存储的底层细节。高级模块OrderHistory依赖于接口 DataStore 来访问数据。较低级别DataStore实现中的任何更改都不会对OrderHistory产生任何影响。 此外,由于模块是松散耦合的,因此可以独立测试它们。可以使用依赖注入轻松地将新实现注入到高级模块中。 这也就是我们经常说的要面向接口变成,而非实现,因为接口意味着契约,更加稳定。 总结以上五项原则构成了软件工程中遵循的最佳实践的基石。在日常工作中实践上述原则有助于提高软件的可读性、模块化、可扩展性和可测试性。 最终,它有助于构建易于理解且维护良好的软件。遵循上述做法有助于提高开发人员的工作效率和工程团队的敏捷性。 |
|