【问题标题】:Is the Open/Closed Principle a good idea? [closed]开放/封闭原则是个好主意吗? [关闭]
【发布时间】:2010-11-27 20:07:15
【问题描述】:

这个问题不是关于 OCP 是什么。而且我也不是在寻找简单的答案。

所以,这就是我问这个的原因。 OCP 在 80 年代后期首次被描述。它反映了当时的思想和背景。令人担忧的是,在代码已经测试并投入生产之后,更改源代码以添加或修改功能最终会带来太大的风险和成本。所以想法是尽可能避免更改现有的源文件,只以子类(扩展)的形式添加到代码库中。

我可能错了,但我的印象是基于网络的版本控制系统(VCS)当时并没有被广泛使用。关键是 VCS 对于管理源代码更改至关重要。

重构的想法是最近才出现的。支持自动化重构操作的复杂 IDE 在当时肯定是不存在的。即使在今天,许多开发人员也没有使用可用的最佳重构工具。这里的重点是,这些现代工具允许开发人员在几秒钟内安全地更改数千行代码。

最后,今天自动化开发人员测试(单元/集成测试)的想法很普遍。有许多免费和复杂的工具支持它。但是,如果我们从不/很少更改现有代码,那么创建和维护一个大型自动化测试套件有什么好处呢?按照 OCP 的要求,新代码只需要新的测试。

那么,OCP 在今天真的有意义吗?我不这么认为。相反,如果新功能不需要新类,我确实更愿意在添加新功能时更改现有代码。这样做将使代码库更简单、更小,并且更容易阅读和理解。破坏先前功能的风险将通过 VCS、重构工具和自动化测试套件进行管理。

【问题讨论】:

  • 导致此问题的冗长评论线程:stackoverflow.com/questions/1379230/…
  • 看起来你只是在寻找一个论据,而且你已经下定决心了。
  • 您提出了一个问题,但您自己回答的方式甚至不鼓励辩论。这可能应该作为“不是一个真正的问题”而关闭,如果不是这样,那么肯定会成为社区维基。
  • 这是个好问题。他是说他对 ZOCP 的印象主要是因为更改生产代码很危险——问题是这是否属实。
  • 这个问题被关闭是难以置信的愚蠢。另一个 StackOverflow 失败。

标签: open-closed-principle


【解决方案1】:

当您不是代码的使用者时,OCP 很有意义。如果我正在编写一个类,并且我或我的团队正在编写所有使用它的类,我同意。随着事情的变化进行重构根本不是什么大不了的事。

另一方面,如果我正在为我的客户编写 API,或者我在一个大型组织中有多个兴趣不同的消费者,那么 OCP 至关重要,因为我无法轻松地重构。

另外,如果你只是重构你的类以满足每个人的需求,你会得到一个臃肿的类。如果你设计的类允许消费者扩展你的类而不是修改它,你就不会有这个问题。

【讨论】:

  • 是的,但是您正在谈论更改已发布的 API。在这种情况下,您显然在可以对公共接口(OCP 与否)进行哪些更改方面受到更多限制。但请注意,您仍然可以安全地更改已发布 API 背后的实现。问题是 OCP 不做任何区分,只是在假设它们不安全的情况下禁止修改。
  • 我真的认为仅将 OCP 应用于 API 有点误解。在开发尚未稳定的应用程序中(即 95% 将完成的所有功能都已完成),基本上它的每个部分都是一个 API - OCP 适用于持续开发,而无需更改使用您的每个组件。跨度>
  • 关键是内部(未发布的)API 几乎可以像封装的实现细节一样容易地进行修改。顺便说一句,“已发布 API”是 Martin Fowler 创造的一个术语,用于描述为未知第三方使用而发布的公共 API(只是为了确保每个人都能理解我所指的内容)。
【解决方案2】:

那么,OCP 在今天真的有意义吗?我不这么认为。

有时会这样:

  • 当您将基类发布给客户并且无法在所有客户的机器上轻松修改时(例如,参见“DLL Hell”)

  • 当您是客户时,没有自己编写基类,也不是维护它的人

  • 更一般地说,基类被多个团队和/或多个项目使用的任何情况

另见Conway's Law

【讨论】:

  • 我同意你的三点,但 OCP 并不是真正关于修改已发布的 API。大多数为应用程序以及大多数可重用类库/框架编写的代码都是内部代码,不会向未知受众公开。 OCP 说即使这样的代码也不应该被修改,这是我不同意的。
  • 显然你可以把这个原则带到荒谬的极端:“没有软件,一旦编写,就应该改变。”但这是事实,即使作为单个项目的独立开发人员,我也发现我的一些早期(例如架构级别)抽象是“稳定的”,并且我不会在开发项目时更改它们(例如通过向子类添加更多实现细节)。
  • 实际上是关于已发布的 API,请参阅我的答案的更新。
  • 最著名的 OCP 文章 objectmentor.com/resources/articles/ocp.pdf 谈到了一个满足原则的模块(第 2 页,此处总结):“Open for Extension”:这意味着我们可以使模块行为随着应用程序需求的变化,以新的和不同的方式。 “封闭修改”:此类模块的源代码不可侵犯;不允许任何人对其源代码进行更改。因此,它不仅仅是甚至主要是关于模块的已发布接口。对我来说,模块源代码不应该被关闭修改。
【解决方案3】:

有趣的问题,基于开放封闭原则的严格定义,我可以看到你来自哪里。

我对开闭原则的定义略有不同,我认为这个原则应该适用,那就是更广泛地应用它。

我想说,我在应用程序中涉及的所有类(作为一个整体)都应该关闭以进行修改并打开以进行扩展。所以原则是,如果我需要改变应用程序的行为和/或操作,我实际上并没有修改一个类,而是添加一个新类,然后更改关系以指向这个新类(当然取决于大小的变化)。如果我遵循单一职责并利用控制反转,则应该发生这种情况。然后发生的是所有的变化都变成了扩展。他们的系统现在可以以以前的方式和新的方式行动,并且在它们之间改变 = 改变关系。

【讨论】:

  • 你可以这样做,但对我来说似乎不实用。从概念上讲,同一个类会有许多不同的版本,所有实现相同接口的独立类将共存于同一个代码库中,这将增长得更快。
【解决方案4】:

这里的重点是,这些现代工具允许开发人员在几秒钟内安全地更改数千行代码。

如果您有“一位”开发人员,这很好。如果您在团队中工作,当然有版本控制,可能还有分支和合并,那么确保来自不同人的更改往往最终集中在不同文件中的能力对于能够控制正在发生的事情非常重要。

您可以想象特定语言的合并/分支工具可以并行完成三个重构,并像更改隔离文件一样轻松地合并它们。但是这样的工具不存在,如果有,我也不想依赖它们。

【讨论】:

  • 好吧,实际上修改数千行代码的自动重构并不经常发生(尽管几年前我在一个大约 8 名开发人员的团队中使用 CVS 做了很多工作)。但关键是这些工具确实允许您以安全的方式进行更改。根据我的经验,不需要分支,并且如果正确完成合并是安全的,即使它有点费力。
  • 好吧,如果操作正确,一切都是安全的,即使是杂耍电锯...关键是自 90 年代以来唯一的实际变化是分支/合并与签入/签出相比越来越受欢迎.没有人使用 OCP,而是在软盘上传递文件……而且扩展分支实际上使扩展重构变得更加困难,而不是更少。
  • 其实我做了很多重构,但从来没有在CVS或SVN中使用过分支。从来不需要。我想你会惊讶地看到我之前的项目是如何执行维护工作的:一个大型、复杂的基于 Java 的业务 Web 应用程序(400 多个持久实体,50​​0 多个用例)。在整整一年的时间里,每天都有大约十几个变更请求应用,每天都有一个新版本在生产中。有 7-9 名开发人员,我不会说他们很出色。这种级别的维护仍在进行中,应用程序仍然在线。我们没有遵循 OCP。
  • 完全正确:因为您没有使用分支,所以重构几乎没有问题。另外,我认为至少有时不遵循 OCP 在物理上是不可能用 Java 编程的。所以我怀疑你对它的含义有某种误解。
  • 好吧,我一直怀疑分支机构的麻烦多于其价值。要遵循 OCP,我必须为所有内容创建单独的抽象,并使所有客户端仅依赖于它们,而不依赖于实现。在我参与的所有项目中,我认为我从来没有真正这样做过,甚至没有看到其他人这样做过。你能更具体地说明那会是什么误解吗? (如果有一个我真的很想知道的话;我想在这里保持开放的态度,但到目前为止的论点似乎表明人们并没有真正阅读 OCP。)
【解决方案5】:

好的,这是我的回应。

我无法证明该原则的历史渊源,但它在现代仍然经常被引用。我不认为改变功能代码是危险的(尽管它当然是)它允许你分离想法。

假设我们有一个组件

public class KnownFriendsFilter{
  IList<People> _friends;
  public KnownFriendsFilter(IList<People> friends) {
    _friends = friends;
  }
  public IList<Person> GetFriends(IList<Person> people) {
    return people.Where(p=>_friends.Contains(p)).ToList();
  }
}

现在说一下这个特定组件需要稍微修改的算法 - 例如,您要确保传入的初始列表包含不同的人。这将是 KnownFriendsFilter 关注的问题,因此请务必更改类。

但是这个类和它支持的特性是有区别的。

  • 这个类实际上只是为已知朋友过滤一组人
  • 它支持的功能是从一组人中找到所有朋友

不同之处在于特性与功能有关,而类与实现有关。我们更改该功能的大多数请求将超出该类的具体职责。

例如,假设我们要添加以字母“X”开头的任何名字的黑名单(因为这些人显然是太空人,而不是我们的朋友)。那是支持该功能的东西,但实际上并不是该课程的全部内容,将其粘在课程中会很尴尬。当下一个请求出现时,现在该应用程序仅由厌恶女性者使用,并且任何女性名字也必须排除在外,该怎么办?现在你必须添加逻辑来决定班级中的名字是男性还是女性 - 或者至少知道其他一些知道如何做到这一点的班级 - 班级的责任越来越大并且变得非常臃肿!那么横切关注点呢?现在我们想在过滤一组人时记录,那也可以吗?

最好分解出一个 IFriendsFilter 接口并将这个类包装在一个装饰器中,或者将它重新实现为 IList 上的责任链。这样,您就可以将这些职责中的每一个放入支持该问题的自己的类中。如果您注入依赖项,那么任何使用此类(以及在我们的应用程序中集中使用)的代码都不需要更改!

再说一遍,原则不是永远不要更改现有代码 - 它是关于不要最终陷入这样一种情况,即您需要在使常用类的职责膨胀或必须编辑使用的每个位置之间做出决定它。

【讨论】:

  • 嗯,你似乎在谈论一些对我来说与 OCP 谈论的非常不同的东西。其中一些功能更改需要对现有类进行修改,例如添加依赖注入点。我认为您可能正在尝试将方形钉 (OCP) 安装到圆孔中(通过添加扩展点使类更加通用)。我不相信 Bertrand Meyer 的想法和你一样。承认OCP应该被丢弃不是更好吗?当然还有很多其他的原则和想法......
  • 如果我们应用的解决方案是装饰器,则除了提取接口之外,根本不需要对类进行任何更改。就像我说的,我对 OCP 没有历史观点,但这就是它在 SOLID 中用作 O 时所指的内容。这都是关于扩展点的。我敦促您参加下一次虚拟 alt.net 会议,顺便说一下,下一次演讲将讨论 Open/Closed virtualaltnet.com/Home/Calendar
  • 是的,Robert Martin 将 OCP 描述为通过使用抽象来实现的东西,其实现类是通过配置/布线选择的,而客户端类仅依赖于抽象。当然,这是一种做事方式,但与简单的做事方式(KISS、YAGNI)相比,这是一种无效的方式。此外,这种方法忽略了这样一个事实,即对功能的许多更改无论如何都需要对抽象进行更改。
  • 一年多以前我就开始接触这个东西了,所以我的经验并不丰富。然而,我已经与无数已经这样做了很长时间的人交谈过。我们都不同意您的说法,即更改通常需要您更改界面。根据我的经验,一个巧妙放置的抽象确实可以吸收大多数不会触发大规模重构的变更请求。至于违反 YAGNI - 是的,确实如此。因此,在需要之前不要创建接口。你有重构工具,对吧?加入现场会议并了解这一点。请
  • 我从未断言更改通常需要您更改界面;我说过“对功能的许多更改将会”。您关于“直到您需要它”才创建接口的想法确实意味着违反了 OCP。 OCP 实际上要求您预先创建接口。请参阅objectmentor.com/resources/articles/ocp.pdf(第 2 页)中的“客户端-服务器”示例。
【解决方案6】:

我从来没有听说过 OCP 是这样的。也许你指的是别的东西,但我知道的 OCP 说“一个模块/类必须为 extension 打开,但为 modification 关闭,这意味着你不应该修改模块的源代码以增强它,但模块或对象应该易于扩展。

想想 eclipse(或任何其他基于插件的软件)。您没有源代码,但任何人都可以编写插件来扩展行为或添加其他功能。你没有修改eclipse,但是你扩展了它。

所以,是的,Open/Closed 原则确实非常有效,而且是个好主意。

更新:

我发现这里的主要冲突是仍在开发中的代码和已经被某人发布和使用的代码之间。于是就去找了Bertrand Meyer,这个原理的作者。他说:

如果一个模块可供其他模块使用,则称该模块已关闭。 这假设 该模块已被赋予定义良好、稳定的描述(其接口在 信息隐藏感)。在实现层面,模块的闭包也 意味着您可以编译它,也许将它存储在库中,并使其可用 供其他人(其客户)使用

因此,确实,开放/封闭原则仅指稳定、可编译和使用的实体。

【讨论】:

  • +1 这就是 OCP 的精髓。
  • 我明白你说的。我也同意为外部使用而发布的模块不应更改其接口。实际上,这很明显(尽管它有时会发生在现实世界中,例如在 ASM 项目中)。但请注意,您引用的定义并未说“已发布模块”。我对 OCP 的问题就是,它似乎禁止更改内部模块。而且我开发的大部分代码都是内部的,没有发布。
  • 换句话说,如果“开放/封闭原则仅指稳定的、可编译和使用的实体”是真的,那么它不是一个很有用的原则。考虑一个典型的定制业务应用程序,这是大多数软件开发项目所涉及的(对我和我认识的大多数开发人员来说,无论如何)。在这样的代码库中,几乎所有(通常是所有)模块都是内部的、未发布的并且几乎不是“稳定的”。结论:在此类项目中,OCP 不适用。
  • 嗯,业务应用程序确实有一个稳定的部分,这就是业务运行的方式。规则可能会改变,但在任何领域都有特定部分保持不变,只是因为它们特定于该业务部分。您应该找到共性并将它们稳定到一个界面中。什么变化,应该只是对它的扩展。还有另一条原则或多或少说“本地化什么变化”。
  • 这是一个文档打印的例子。文档要经过很多过程:创建、格式化、打样、打印、在纸上打孔、缝合等。并非所有文档都经过所有这些过程。因此,您创建了一个工作流程基础架构,您只需更改进入该工作流程的流程和顺序。瞧!我刚刚应用了开放/封闭原则。没有发布任何内容,没有 API,甚至没有代码。我刚刚谈到了我的业务领域中的架构。我相信你也可以在你的域中应用它。
猜你喜欢
  • 1970-01-01
  • 2016-06-10
  • 1970-01-01
  • 2021-02-01
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 2018-11-17
相关资源
最近更新 更多