开闭原则是个好主意吗?

发布于 2024-08-04 18:47:30 字数 644 浏览 2 评论 0 原文

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

所以,这就是我问这个的原因。 OCP 首次描述于 80 年代末。它反映了当时的思想和背景。人们担心的是,在代码经过测试并投入生产后,更改源代码以添加或修改功能,最终风险太大且成本高昂。因此,我们的想法是尽可能避免更改现有源文件,而仅以子类(扩展)的形式添加到代码库中。

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

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

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

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

This question is not about what OCP is. And I am not looking for simplistic answers, either.

So, here is why I ask this. OCP was first described in the late 80s. It reflects the thinking and context of that time. The concern was that changing source code to add or modify functionality, after the code had already been tested and put into production, would end up being too risky and costly. So the idea was to avoid changing existing source files as much as possible, and only add to the codebase in the form of subclasses (extensions).

I may be wrong, but my impression is that network-based version control systems (VCS) were not widely used back then. The point is that a VCS is essential to manage source code changes.

The idea of refactoring is much more recent. The sophisticated IDEs that enable automated refactoring operations were certainly inexistent back then. Even today, many developers don't use the best refactoring tools available. The point here is that such modern tools allow a developer to change literally thousands of lines of code, safely, in a few seconds.

Lastly, today the idea of automated developer testing (unit/integration tests) is widespread. There are many free and sophisticated tools that support it. But what good is creating and maintaining a large automated test suite if we never/rarely change existing code? New code, as the OCP requires, will only require new tests.

So, does the OCP really makes sense today? I don't think so. Instead, I would indeed prefer to change existing code when adding new functionality, if the new functionality does not require new classes. Doing so will keep the codebase simpler, smaller, and much easier to read and understand. The risk of breaking previous functionality will be managed through a VCS, refactoring tools, and automated test suites.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

新一帅帅 2024-08-11 18:47:30

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

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

另外,如果您只是重构您的类来满足每个人的需求,您将得到一个臃肿的类。如果您设计的类允许使用者扩展您的类而不是修改它,那么您就不会真正遇到此问题。

OCP makes a lot of sense when you aren't the consumer of your code. If I'm writing a class, and I or my team am writing all of the classes which consume it, I agree. Refactoring as things change is no huge deal at all.

If, on the other hand, I am writing an API for my customers, or I have multiple consumers in a large organization with varying interests, the OCP is critical because I can't refactor as easily.

Also, if you just refactor your class to meet everyone's needs, you'll get a bloated class as a result. If you designed the class to allow consumers to extend your class rather than modify it, you wouldn't really have this problem.

也只是曾经 2024-08-11 18:47:30

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

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

所以,是的,开放/关闭原则确实非常有效并且是一个好主意

更新:

我看到了主要内容 。这里的冲突是仍在开发的代码和已经被某人发布和使用的代码之间的,所以我去检查了 Bertrand Meyer,该原则的作者,他说:

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

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

I never heard of OCP being that. Maybe you are refering to something else, but the OCP I know says "A module/class must be open for extension, but closed for modification, meaning that you shouldn't modify the source code of the module to enhance it, but the module or object should be easy to extend.

Think of eclipse (or any other plugin based software for that matter). You don't have the source code, but anyone can write a plugin to extend the behaviour or to add another feature. You didn't modify eclipse, but you extended it.

So, yes, the Open/Closed principle is indeed very valid and quite a good idea.

UPDATE:

I see that the main conflict here is between code that is still under development and code that is already shipped and used by someone. So I went and checked with Bertrand Meyer, the author of this principle. He says:

A module is said to be closed if it is available for use by other modules. This assumes
that the module has been given a well-defined, stable description
(its interface in the
sense of information hiding). At the implementation level, closure for a module also
implies that you may compile it, perhaps store it in a library, and make it available
for others (its clients) to use
.

So, indeed, the Open/Closed Principle refers only to stable, ready for compile and use entities.

浴红衣 2024-08-11 18:47:30

好吧,这就是我的回应。

我无法证实这一原则的历史渊源,但它在现代仍然被频繁引用。我不认为更改功能代码是危险的(尽管当然是危险的),而是允许您分离出想法。

假设我们有一个组件,

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 上的责任链。这样,您就可以将每个职责放入自己的类中,以支持该关注点。如果您注入依赖项,那么任何使用此类(及其在我们的应用程序中集中使用)的代码都无需更改!

再说一次,这个原则并不是永远不改变现有的代码,而是不要最终面临这样的情况:是让常用类的职责变得臃肿,还是必须编辑使用它的每个位置。

Alright, so here's my response.

I cannot testify to the historic origin of the principle but it is still invoked frequently in modern times. I don't think its about it being dangerous to change functioning code (though it of course is) its about allowing you to separate out ideas.

Suppose we have a component

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();
  }
}

Now say the algorithm on which this specific component needs a slight modification - for example you want to make sure that the initial list passed in contains distinct people. This is something that would be a concern of the KnownFriendsFilter so by all means change the class.

However there is a difference between this class and the feature it supports.

  • This class is really just to filter an array of people for known friends
  • The feature that it supports is to find all friends from an array of people

The difference is that the feature is concerned with function while the class is concerned with implementation. Most requests we get to change that feature will fall outside the specific responsibility of the class.

For example lets say we want to add a blacklist of any names that begin with the letter "X" (because those people are obviously spacemen and not our friends). That's something that supports the feature but is not really a part of what this class is all about, sticking it in the class would be awkward. What about when the next request comes in that now the application is being used exclusively by misogynists and any female names must be also excluded? Now you've got to add logic to decide whether the name is male or female into the class - or at least know about some other class that knows how to do that - the class is growing in responsibilities and becoming very bloated! And what about cross-cutting concerns? Now we want to log whenever we filter an array of people, does that go right in there too?

It would be better to factor out an IFriendsFilter interface and wrap this class in a decorator, or re-implement it as a chain of responsibility on IList. That way you can place each of those responsibilities into their own class that supports just that concern. If you inject dependencies then any code that uses this class (and its centrally used in our application) doesn't have to change at all!

So again, the principle isn't about never changing existing code - it's about not ending up in a situation where you are faced with the decision between bloating the responsibilities of a commonly used class or having to edit every single location that uses it.

冰火雁神 2024-08-11 18:47:30

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

有时确实如此:

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

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

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

另请参阅康威定律

So, does the OCP really makes sense today? I don't think so.

Sometimes it does:

  • When you've released the base class to customers and can't easily modify it on all your customers' machines (see for example "DLL Hell")

  • When you're a customer, who didn't write the base class yourself and aren't the one maintaining it

  • More generally, any situation where the base class is used by more than one team and/or for more than project

See also Conway's Law.

烟花肆意 2024-08-11 18:47:30

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

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

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

Interesting question, and based on the strict definition of the open closed principle I can see where you're coming from.

I have come to define the open-closed principle slightly differently, and this principle I think should apply, and that is to apply it far more broadly.

I like to say, all my classes (as a whole) involved in the application should be closed for modification and open for extension. So the principle is that if I need to change behaviour and/or operation of the application, I do not in fact modify a class but add a new one and then change the relationships to point to this new one (depending of course on the size of the change). If I'm following the single responsibility and utilising inversion of control, this should occur. What then occurs is that all changes come to be extensions. They system now can both act in the former way and in the new way and changing between them = changing a relationship.

_蜘蛛 2024-08-11 18:47:30

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

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

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

The point here is that such modern tools allow a developer to change literally thousands of lines of code, safely, in a few seconds.

Which is fine if you have 'a' developer. If you are working in teams, certainly with version control, probably with branching and merging, then the ability to ensure that changes from different people tend to end up concentrated in different files is pretty vital to being able to control what's going on.

You could imagine language-specific merge/branch tools that could take three refactorings done in parallel and merge them as easily as changes to isolated files. But such tools don't exist, and if they did, I wouldn't want to rely on them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文