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

时间:2009-03-18 12:08:58

标签: java open-closed-principle

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

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

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

7 个答案:

答案 0 :(得分:25)

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

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

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

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

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

就个人而言,我个人认为继承只不过是现在的实施细节。

答案 1 :(得分:8)

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

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

答案 2 :(得分:7)

open-closed principle(打开扩展名,关闭以进行修改)中,您仍然可以使用最终修饰符。这是一个例子:

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接口的东西。这个技巧是依赖注入的变种,因为我们用另一个来提供封闭的类,在这种情况下是通过构造函数。由于扩展名为interface,因此不能为final,但其实现类可以是。

使用final on classes在java中关闭它们类似于在C#中使用sealed。在.NET端有关于它的similar discussions

答案 3 :(得分:5)

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

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

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

你可以称之为“依赖注入”,或“策略模式”或“访客模式”或其他什么,但它归结为使用组合而不是继承来避免重复。

答案 4 :(得分:4)

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

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

答案 5 :(得分:3)

这两个陈述

  

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

  

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

并不是彼此直接矛盾。只要你为它设计和记录,你就可以遵循开放原则(按照布洛赫的建议)。

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

答案 6 :(得分:1)

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

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

正如第一个答案中指出的那样,这具有历史根源。几十年前,继承得到了支持,开发人员测试闻所未闻,重新编译代码库通常需要很长时间。

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

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