Open/Closed 原则 - 如何调用新版本?

Posted

技术标签:

【中文标题】Open/Closed 原则 - 如何调用新版本?【英文标题】:Open / Closed principle - How to call the new versions? 【发布时间】:2012-05-23 20:12:22 【问题描述】:

我正在尝试掌握 Open/Closed 原则(在我的 php 案例中,但这并不会真正产生影响)。

我的理解是一个类从不开放修改。仅用于错误修复。如果我想向该类添加新代码,那么我必须创建一个新代码并扩展“旧”类。这是我可以添加新代码的唯一方法。

在某种程度上我可以看到这样做的好处。因为基本上你创建了某种版本控制系统,旧代码总是可以工作,但你也可以尝试使用新类。

但这在实践中如何运作?我的意思是,假设我有以下课程:

class MyObject

    public function doSomething()
    
        echo 'Im doing something';
    

所以我可能在某个地方实例化了这个类:

$obj = new MyObject();

但后来我决定在该对象中使用另一种方法是件好事。所以我也可以做点别的。根据 OCP,我无法修改课程。所以我必须创建一个新的,延伸到旧的,对吧?

第一个问题。我如何称呼新班级?因为它实际上并不是一个完整的新对象。像。用户对象是用户对象。我不能因为它需要另一种方法就突然给它起一个完全不同的名字。无论如何,我创建了新类:

class MyNewObject extends MyObject

    public function doSomethingElse()
    
        echo 'Im doing something else now';
    

现在这也意味着我必须更改实例化“MyObject”类的代码行并将其替换为“MyNewObject”类,对吧..?如果这在不止一个地方完成,那么我必须搜索我的源代码......(想想控制器类中的一个方法,它几乎总是使用'​​new'关键字来实例化某些类)。

这同样适用于继承。我必须找到继承旧类的每个类,并且必须用新类替换它。


所以基本上我的问题是:

如何命名具有新方法的新类?只是因为我添加了一些新功能,并不意味着我可以给这个类一个全新的名字......

如果“旧”类是从多个地方实例化(或继承)的呢?那么我必须找到所有这些地方......哪里有收获?

【问题讨论】:

$x = new MyObj_version_3.14159265()?嗯。馅饼。 请注意,Open/closed principle 有两种解释方式。您似乎主要考虑的是较旧的(Meyers),而现在该用例通常由委托处理。 用一个更现实的例子来回答这个问题会更容易。我认为命名等会更明显。 【参考方案1】:

开放封闭原则并非旨在用作一种版本控制系统。如果您确实需要对课程进行更改,请继续进行这些更改。您无需创建新类并更改所有实例化旧类的位置。

开放封闭原则的要点是,精心设计的系统不应要求您更改现有功能以添加新功能。如果您要向系统添加新类,则无需搜索所有代码来查找需要引用该类或有特殊情况的位置。

如果您的类的设计不够灵活,无法处理某些新功能,那么请务必更改您的类中的代码。但是当您更改代码时,请使其灵活,以便您可以在将来处理类似的更改而无需更改代码。它是一种设计策略,而不是一套阻止你做出改变的手铐。有了良好的设计决策,随着时间的推移,当您向系统添加新功能时,您现有的代码将需要越来越少的更改。这是一个迭代过程。

【讨论】:

【参考方案2】:

我认为通过添加一个函数,你并没有修改类的行为。

在您的应用程序中当前正在调用 doSomething() 的所有实例中,只需将 doSomethingElse() 添加到类中将不起作用。由于您没有更改 doSomething(),因此行为与以前相同。

一旦您确定您的 doSomething() 实现在某些情况下没有削减它,您就可以扩展该类并覆盖 doSometing()。同样,原来的行为仍然与往常一样,但现在您也可以使用新的 doSomething() 了。

我意识到这违背了开放/封闭的严格定义,但这是现实世界,这就是我在代码中解释该原则的方式。

【讨论】:

我同意,添加新方法并没有那么糟糕。不在我给出的示例中。但是,如果我需要对方法进行一些代码破坏性更改,那么我必须创建一个新类来执行我想要的新事物。但是我的两个问题仍然存在。我如何命名新班级?这是否意味着我必须搜索我的整个代码以找出我实例化旧类的位置,以便我可以用新类更改它?最后一个实际上是最困扰我的......因此我看不出这个原则有什么好处。 你应该反过来想:如果你的子类没有明显的名字,那么也许你不应该做它!子类不应该用来修复损坏的类——损坏的类应该被修复,依赖于损坏行为的调用代码也应该被修复。 (也就是说,有时您需要子类化来修复损坏的类,但这通常是因为您无法控制父类或其调用代码。) 顺便说一句,创建子类有两个很好的理由:创建一个更抽象的基类的新实现(多态继承),或者创建一个具有额外功能但遵循Liskov Substitution Principle 的子类型.在这两种情况下,新名称都应该是显而易见的。【参考方案3】:

您需要在 MyNewObject 类中创建一个构造函数,该构造函数调用父类的构造函数:

function __construct() 

    parent::__construct();


通过这种方式,您可以实例化新类,并且仍然可以访问扩展类的所有功能。

然后您还可以覆盖父类中的任何函数(当然只要它没有标记为 final)。

所以你可以这样做:

$newObj = new MyNewObject();

$newObj->doSomething();

$newObj->doSomethingElse();

【讨论】:

是的,我知道我必须调用父构造函数。否则继承不会有多大用处:) --- 但在你的例子中,你还创建了一个“MyNewObject”实例而不是“MyObject”。这当然是有道理的,因为您想使用新功能。但这也意味着您必须更改代码中曾经实例化“MyObject”的每一行。您现在必须将其更改为“MyNewObject”。如果您必须搜索整个代码以将旧实例更改为新实例,这对我来说并没有真正的 OO。 是的,我明白你的意思。请原谅我假设您对 OOP 有点陌生。

以上是关于Open/Closed 原则 - 如何调用新版本?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Open/Closed 原则或策略模式重构此 ruby​​ 代码

Open/Closed 原则和违反封装性

开闭原则 Open Closed Principle

开放封闭原则(Open Closed Principle)

设计模式六大原则: 开闭原则(Open Closed Principle)

如何在SOLID中从条件语句转换为OCP(Open Closed Principle)?