在实践中如何遵守开闭原则


14

我了解开放原则的意图。这是通过告诉您尝试在不进行修改的情况下进行扩展,以降低破坏已在修改过程中起作用的内容的风险。

但是,我在理解此原理在实践中的应用时遇到了一些麻烦。据我了解,有两种方法可以应用它。在可能的更改之前和之后:

  1. 之前:编程抽象并尽可能“预测未来”。例如,drive(Car car)如果将来将Motorcycles添加到系统中,则必须更改 方法,因此它可能违反了OCP。但是该方法drive(MotorVehicle vehicle)将来不太可能需要更改,因此它遵循OCP。

    但是,很难预测未来并提前知道将对系统进行哪些更改。

  2. 之后:需要更改时,扩展类而不是修改其当前代码。

练习1并不难理解。但是,在实践2中,我很难理解如何申请。

例如(我从YouTube视频中获取了它):假设我们在类中有一个接受CreditCard对象的方法:makePayment(CraditCard card)。将一天Voucher添加到系统中。此方法不支持它们,因此必须对其进行修改。

首先实现该方法时,我们无法预测未来,无法以更抽象的术语进行编程(例如makePayment(Payment pay),因此现在我们必须更改现有代码。

练习2说,我们应该通过扩展而不是修改来添加功能。这意味着什么?我应该继承现有的类而不是简单地更改现有的代码吗?我是否应该围绕它进行某种包装,以避免重写代码?

还是该原则甚至不涉及“如何正确修改/添加功能”,而是涉及“如何避免必须首先进行更改(即从程序到抽象)”?



1
“打开/关闭原则”并不决定您使用的机制。继承通常是错误的选择。而且,不可能防范所有未来的变化。最好不要试图预测未来,但是一旦需要更改,请修改设计,以便可以容纳相同类型的未来更改。
2014年

Answers:


14

设计原则始终必须相互平衡。您无法预测未来,大多数程序员在尝试时都会做得很糟糕。这就是为什么我们有3条规则,这主要是关于重复,但是也适用于任何其他设计原则的重构。

当您只有一个接口的实现时,您无需过多关心OCP,除非可以清楚地知道在哪里进行扩展。实际上,在这种情况下尝试进行过度设计时,您常常会失去清晰度。扩展一次后,如果这是最简单,最清晰的方法,则可以对其进行重构以使其对OCP友好。当您将其扩展到第三个实现时,即使需要更多的精力,也请确保考虑到OCP对其进行重构。

实际上,当您只有两个实现时,添加第三个实现时进行重构通常不太难。当您让它超过该点时,维护变得很麻烦。


1
谢谢回答。让我看看我是否理解您的意思:您的意思是,我应该主要强迫我更改课程后才关心OCP 。含义:第一次实现类时,我不必担心OCP,因为无论如何很难预测未来。当我第一次需要扩展/修改它时,也许最好重构一下以使其在将来更加灵活(更多OCP)。第三次,我需要扩展/修改类,是时候进行一些重构以使其更符合OCP了。你是这个意思吗?
阿维夫·科恩

1
那是主意。
卡尔·比勒费尔德

2

我认为您对未来的期望太高了。以灵活的方式解决当前问题,该方式坚持打开/关闭。

假设您需要实现一个drive(Car car)方法。根据您的语言,您有两种选择。

  • 对于支持重载(C ++)的语言,只需使用 drive(const Car& car)

    稍后您可能会需要drive(const Motorcycle& motorcycle),但是不会干扰drive(const Car& car)。没问题!

  • 对于不支持重载的语言(目标C),请在方法中包含类型名称-driveCar:(Car *)car

    稍后,您可能需要-driveMotorcycle:(Motorcycle *)motorcycle,但同样,它也不会干扰。

这允许drive(Car car)封闭修改,但可以扩展到其他类型的车辆。这个极简的未来计划可以让您今天完成工作,但又可以避免将来受到阻碍。

尝试想象您需要的最基本的类型会导致无限回归。当您想要驾驶Segue,自行车或超大型飞机时会发生什么。您如何构造一个单一的通用抽象类型,该类型可以说明人们进入并用于移动性的所有设备?


修改类以添加新方法违反了“开放式封闭原则”。您的建议还消除了将Liskov替代原理应用于所有可驾驶车辆的能力,这实际上消除了OO最强大的部分之一。
Dunk 2014年

@Dunk我的答案基于多态的打开/关闭原则,而不是严格的Meyer打开/关闭原则。可以更新类以支持新的接口。在此示例中,汽车接口与摩托车接口保持分离。这些可以形式化为实现类可以支持的汽车和摩托车的单独驾驶抽象类。
杰弗里·托马斯

@Dunk Liskov替代原理很有用,但并非免费提供。如果原始规范仅需要汽车,那么制造更通用的汽车可能不值得花额外的金钱,时间和复杂性。此外,更通用的车辆不太可能完全适合处理计划外的子类。要么需要将摩托车的接口塞入车辆接口(该接口仅设计用于处理汽车),要么需要修改车辆以处理摩托车(确实违反了开/关)。
杰弗里·托马斯

Liskov-Substitution原则不是免费提供的,但也没有带来太多成本。通常,即使从未在主应用程序中继承另一个子类,它的回报也远比其花费的成本高很多倍。应用LSP使自动化测试变得更加容易,这已经是一个胜利。另外,尽管您当然不应该大吃一惊并假设一切都将需要LSP,但是如果您正在构建应用程序并且对未来的版本中可能需要的内容没有很好的感觉,那么您就不必对您的应用程序或其域足够了解。
Dunk 2014年

1
关于OCP的定义。可能是我所从事的行业,与普通的商业公司相比,它们往往要求更高的验证级别,但是通常来说,如果文件/类发生更改,那么不仅需要重新测试文件/类,还需要重新测试所有在您的回归测试中使用该文件/类。因此,如果有人说多态的打开/关闭没问题,那就没关系了,更改接口会产生各种各样的后果,所以还不是很好。
Dunk 2014年

2

我了解开放原则的意图。这是通过告诉您尝试在不进行修改的情况下进行扩展,以降低破坏已在修改过程中起作用的内容的风险。

这还与不通过更改现有对象的行为来破坏所有依赖该方法的对象有关。一旦对象通告了行为更改,就很危险,因为您要更改对象的已知和预期行为,而又不确切知道其他对象希望该行为如何。

这意味着什么?我应该继承现有类,而不是简单地更改其现有代码吗?

对。

通过该类的公共界面,“仅接受信用卡”被定义为该类行为的一部分。程序员向全世界宣布,该对象的方法仅使用信用卡。她使用的方法名称不是特别明确,但确实做到了。系统的其余部分都依赖于此。

当时这可能是合情合理的,但是现在,如果需要更改,您应该开设一个新课程,接受除信用卡以外的其他东西。

新行为=新班级

顺便说一句 -预测未来的一个好方法是想想的名字你给的方法。您是否给了一个真正通用的探测方法名称,例如makePayment,该方法在方法中具有特定的规则,具体说明了该方法可以进行哪些付款?那是代码的味道。如果您有特定的规则,则应从方法名称中明确这些规则-makePayment应该为makeCreditCardPayment。第一次编写对象时,请执行此操作,其他程序员将对此表示感谢。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.