开放封闭原则和Java“最终”修饰符

开放封闭原则指出“软件实体(类,模块,function等)应该是可以扩展的,但是对于修改是封闭的”。

然而,Joshua Bloch在其着名的书“Effective Java”中给出了以下建议:“inheritance的设计和文档,或者禁止它”,并鼓励程序员使用“final”修饰符来禁止子类化。

我认为这两个原则显然是相互矛盾的(我错了吗?)。 在编写代码时,您遵循哪个原则,为什么? 你是否打开你的课程,不允许inheritance你的课程(哪些?),或尽可能使用最终修饰语?

坦率地说,我认为开放/封闭原则更不合时宜。 它从80年代和90年代开始,当OO框架建立在一切必须从其他东西inheritance并且一切都应该是可子类化的原则的基础上。

这在MFC和Java Swing这个时代的UI框架中最为典型。 在Swing中,你有一个荒谬的inheritance,其中(iirc)按钮扩展复选框(或反过来)给出其中一个未使用的行为(我认为它是复选框上的setDisabled()调用)。 为什么他们分享祖先? 没有其他原因,他们有一些共同的方法。

这些天的成分比inheritance更受青睐。 虽然Java默认允许inheritance,但.Net采用(更现代)的方法默认禁止它,我认为这更正确(并且更符合Josh Bloch的原则)。

DI / IoC还进一步提出了组合案例。

Josh Bloch还指出inheritance打破了封装并提供了一些很好的例子。 还有人certificate,如果通过委派而不是扩展类来改变Java集合的行为更加一致。

就个人而言,我在很大程度上认为inheritance只不过是现在的实施细节。

我不认为这两个陈述相互矛盾。 类型可以打开以进行扩展,并且仍然可以关闭以进行inheritance。

一种方法是使用dependency injection。 类型可以在创建时提供这些类型,而不是创建自己的帮助器类型的实例。 这允许您在不更改类型本身的情况下更改类型的部件(即打开以进行扩展)(即关闭以进行修改)。

在开放闭合原则 (打开扩展,关闭以进行修改)中,您仍然可以使用最终修改器。 这是一个例子:

public final class ClosedClass { private IMyExtension myExtension; public ClosedClass(IMyExtension myExtension) { this.myExtension = myExtension; } // methods that use the IMyExtension object } public interface IMyExtension { public void doStuff(); } 

ClosedClass在类内部进行修改时关闭,但是通过另一个进行扩展。 在这种情况下,它可以是任何实现IMyExtension接口的东西。 这个技巧是dependency injection的变种,因为我们用另一个来提供封闭的类,在这种情况下是通过构造函数。 由于扩展是一个interface它不能是final但它的实现类可以。

使用final on class在java中关闭它们类似于在C#中使用sealed 。 在.NET方面有类似的讨论 。

现在我默认使用final修饰符,几乎是reflection性地作为样板的一部分。 当您知道给定的方法将始终如您正在查看的代码中所见时,它会使事情更容易推理。

当然,有时会出现类层次结构正是您想要的情况,然后不使用它就很愚蠢。 但要害怕超过两个级别的层次结构,或者非抽象类进一步子类化的层次结构。 一个类应该是抽象的或最终的。

大多数时候,使用构图是要走的路。 将所有常用机器放入一个类中,将不同的案例放入不同的类中,然后将实例合并为一个整体。

你可以称之为“dependency injection”,或“策略模式”或“访问者模式”或其他什么,但它归结为使用组合而不是inheritance来避免重复。

我非常尊重Joshua Bloch,我认为Effective Java几乎是Java圣经 。 但我认为自动默认为private通常是一个错误。 我倾向于默认protected它们,以便至少可以通过扩展类来访问它们。 这主要源于对unit testing组件的需求,但我也发现它可以替代类的默认行为。 当我在自己公司的代码库中工作并最终不得不复制和修改源代码时,我发现它非常烦人,因为作者选择“隐藏”所有内容。 如果它完全在我的权力,我游说改变访问权限,以避免重复,这是更糟糕的恕我直言。

还要记住,Bloch的背景是设计非常 公开的基岩API库; 获取此类代码“正确”的标准必须设置得非常高,因此很可能与您编写的大多数代码的情况不同。 诸如JRE之类的重要库往往更具限制性,以确保语言不被滥用。 查看JRE中所有已弃用的 API? 改变或删除它们几乎是不可能的。 您的代码库可能不是一成不变的,所以如果事实certificate您最初犯了错误,那么您确实有机会解决问题。

这两个陈述

软件实体(类,模块,函数等)应该是可以扩展的,但是关闭以进行修改。

inheritance的设计和文档,否则禁止它。

并不是彼此直接矛盾。 只要您为其设计和记录,您就可以遵循开放式原则(根据Bloch的建议)。

我不认为Bloch声明你应该更喜欢使用final修饰符禁止inheritance,只是你应该明确选择允许或禁止你创建的每个类中的inheritance。 他的建议是你应该考虑并自己决定,而不是只接受编译器的默认行为。

我不认为最初提出的开放/封闭原则允许解释最终类可以通过注入依赖项来扩展。

根据我的理解,原则是不允许直接更改已投入生产的代码,并且在允许修改function的同时实现该function的方法是使用实​​现inheritance。

正如第一个答案中所指出的,这具有历史根源。 几十年前,inheritance得到了支持,开发人员测试闻所未闻,重新编译代码库经常花费太长时间。

另外,考虑到在C ++中,类的实现细节(特别是私有字段)通常在“.h”头文件中公开,因此如果程序员需要更改它,则所有客户端都需要重新编译。 请注意,Java或C#等现代语言并非如此。 此外,我不认为当时的开发人员可以依赖能够执行动态依赖性分析的复杂IDE,从而避免频繁完全重建的需要。

根据我自己的经验,我更倾向于做相反的事情:“默认情况下,类应该关闭以进行扩展( final ),但是可以打开进行修改”。 想想看:今天我们喜欢版本控制等实践(可以很容易地恢复/比较类的先前版本), 重构 (鼓励我们修改代码以改进设计,或者作为引入新function的前奏),以及开发人员测试 ,在修改现有代码时提供安全网。