Open-closed principle and Java "final" modifier
Asked Answered
C

7

25

The open-closed principle states that "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".

However, Joshua Bloch in his famous book "Effective Java" gives the following advice: "Design and document for inheritance, or else prohibit it", and encourages programmers to use the "final" modifier to prohibit subclassing.

I think these two principles clearly contradict each other (am I wrong?). Which principle do you follow when writing your code, and why? Do you leave your classes open, disallow inheritance on some of them (which ones?), or use the final modifier whenever possible?

Cyprinoid answered 18/3, 2009 at 12:8 Comment(0)
P
29

Frankly I think the open/closed principle is more an anachronism than not. It sems from the 80s and 90s when OO frameworks were built on the principle that everything must inherit from something else and that everything should be subclassable.

This was most typified in UI frameworks of the era like MFC and Java Swing. In Swing, you have ridiculous inheritance where (iirc) button extends checkbox (or the other way around) giving one of them behaviour that isn't used (I think it's its the setDisabled() call on checkbox). Why do they share an ancestry? No reason other than, well, they had some methods in common.

These days composition is favoured over inheritance. Whereas Java allowed inheritance by default, .Net took the (more modern) approach of disallowing it by default, which I think is more correct (and more consistent with Josh Bloch's principles).

DI/IoC have also further made the case for composition.

Josh Bloch also points out that inheritance breaks encapsulation and gives some good examples of why. It's also been demonstrated that changing the behaviour of Java collections is more consistent if done by delegation rather than extending the classes.

Personally I largely view inheritance as little more than an implemntation detail these days.

Pleasure answered 18/3, 2009 at 12:17 Comment(5)
Can you explain what you mean by "disallowing [inheritance] by default"?Philodendron
C# methods are sealed (non-virtual) by default. You have to explicitly mark them as virtual. Shame the same isn't true for classes.Hawse
en.wikipedia.org/wiki/Comparison_of_C_Sharp_and_Java Methods in C# are non-virtual by default. In Java they are virtual by default.Pleasure
In C# the default is that methods are inherited (a subclass has the same method implementation as the super), and in Java the default is that methods may be overridden (only final methods require a subclass to have the same implementation as the super). Therefore C# forces inheritance by default.Worthy
Pete: Inheritance isn't very useful when you have nothing to override.Cannery
V
10

I don't think the two statements contradict each other. A type can be open for extension and still be closed for inheritance.

One way to do this is to employ dependency injection. Instead of creating instances of its own helper types, a type can have these supplied upon creation. This allows you to change the parts (i.e. open for extension) of the type without changing the type itself (i.e. close for modification).

Vazquez answered 18/3, 2009 at 12:13 Comment(3)
I agree. I think that what is happening here is that someone is trying to do computing with their left brain. Left brain doesn't understand computers - although it can be useful when debugging.Betthezel
When down voting, please leave a comment. Thanks.Vazquez
This answer talks about a different kind of "extension" than the one OCP talks about. In the case of the latter, it's extension by subclassing exclusively. So, obviously a class can't be open for extension (in the OCP sense) and closed for inheritance at the same time. If you ever have the opportunity, look at how Meyer (OCP's original author) talks about it in the book ("Object-oriented software construction"): it's all about inheritance.Ewan
I
7

In open-closed principle (open for extension, closed for modification) you can still use the final modifier. Here is one example:

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

The ClosedClass is closed for modification inside the class, but open for extension through another one. In this case it can be of anything that implements the IMyExtension interface. This trick is a variation of dependency injection since we're feeding the closed class with another, in this case through the constructor. Since the extension is an interface it can't be final but its implementing class can be.

Using final on classes to close them in java is similar to using sealed in C#. There are similar discussions about it on the .NET side.

Income answered 18/3, 2009 at 12:15 Comment(2)
This example looks more like DIP than OCP, though. The problem here is that OCP, according to its original definition, does not seem to admit other forms of extension; it requires the use of implementation inheritance. I don't know, maybe we could have an OCP 2.0 8^}Ewan
Open for extension should mean that we can inherite from that class, but when we use final it's become impossible to extends.Niemann
I
6

I respect Joshua Bloch a great deal, and I consider Effective Java to pretty much be the Java bible. But I think that automatically defaulting to private access is often a mistake. I tend to make things protected by default so that they can at least be accessed by extending the class. This mostly grew out of a need to unit test components, but I also find it handy for overriding the default behavior of classes. I find it very annoying when I'm working in my own company's codebase and end up having to copy & modify the source because the author chose to "hide" everything. If it's at all in my power, I lobby to have the access changed to protected to avoid the duplication, which is far worse IMHO.

Also keep in mind that Bloch's background is in designing very public bedrock API libraries; the bar for getting such code "correct" must be set very high, so chances are it's not really the same situation as most code you'll be writing. Important libraries such as the JRE itself tend to be more restrictive in order to ensure that the language is not abused. See all the deprecated APIs in the JRE? It's almost impossible to change or remove them. Your codebase is probably not set in stone, so you do have the opportunity to fix things if it turns out you made a mistake initially.

Independent answered 18/3, 2009 at 12:20 Comment(7)
Actually, if you have private access on attributes, they usually should be mutable through methods or accesible through getters or setters in the super class. You're not really supposed to unit test the actual state of a class, it's more useful to test the behaviour of the class instead.Income
@spoike- what makes you think I was talking about fields? I'm talking (mostly) about using protected access for methods, and not declaring classes "final".Independent
@eljenso-assuming we're all adults, changing the access to protected does not mean we've suddenly decided to throw away encapsulation. There's nothing stopping you now from modifying private members via AccessibleObject anyway. Again, there's a different set of expectations for public library APIs.Independent
@limbic It's not always about what's possible or not, it's also about intent. And maybe the original authors didn't want you to see the internals of their class. Maybe they just messed up the design by making it rigid and unextensible.Dysuria
+1. The prevailing Java architecture-wonk attitude that “anyone who wants to extend your classes is to be considered an enemy and must be kept within a restrictive box” is infuriating and bears no resemblence to the real-world needs of coders who are not working on “very public bedrock APIs”.Tauromachy
@Tauromachy Resistance is futile. You will be encapsulated.Dysuria
You can always use Groovy to write your unit tests: Groovy completely ignores the "private" keyword! Very handy, albeit slightly dangerous, like juggling chainsaws.Hacienda
S
5

Nowadays I use the final modifier by default, almost reflexively as part of the boilerplate. It makes things easier to reason about, when you know that a given method will always function as seen in the code you're looking at right now.

Of course, sometimes there are situations where a class hierarchy is exactly what you want, and it would be silly not to use one then. But be scared of hierarchies of more than two levels, or ones where non-abstract classes are further subclassed. A class should be either abstract or final.

Most of the time, using composition is the way to go. Put all the common machinery into one class, put the the different cases into different classes, then composit instances to have working whole.

You can call this "dependency injection", or "strategy pattern" or "visitor pattern" or whatever, but what it boils down to is using composition instead of inheritance to avoid repetition.

Shriver answered 18/3, 2009 at 12:32 Comment(0)
B
4

The two statements

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

and

Design and document for inheritance, or else prohibit it.

are not in direct contradiction with one another. You can follow the open-closed principle as long as you design and document for it (as per Bloch's advice).

I don't think that Bloch states that you should prefer to prohibit inheritance by using the final modifier, just that you should explicitly choose to allow or disallow inheritance in each class you create. His advice is that you should think about it and decide for yourself, instead of just accepting the default behavior of the compiler.

Banebrudge answered 18/3, 2009 at 13:18 Comment(1)
True. Unfortunately, almost all Java developers make all classes they create non-final by default; and they sure don't design nor document those classes for inheritance... So, my advice is: if in doubt, make it final.Ewan
C
3

I don't think that the Open/closed principle as originally presented allows the interpretation that final classes can be extended through injection of dependencies.

In my understanding, the principle is all about not allowing direct changes to code that has been put into production, and the way to achieve that while still permitting modifications to functionality is to use implementation inheritance.

As pointed out in the first answer, this has historical roots. Decades ago, inheritance was in favor, developer testing was unheard of, and recompilation of the codebase often took too long.

Also, consider that in C++ the implementation details of a class (in particular, private fields) were commonly exposed in the ".h" header file, so if a programmer needed to change it, all clients would require recompilation. Notice this isn't the case with modern languages like Java or C#. Besides, I don't think developers back then could count on sophisticated IDEs capable of performing on-the-fly dependency analysis, avoiding the need for frequent full rebuilds.

In my own experience, I prefer to do the exact opposite: "classes should be closed for extension (final) by default, but open for modification". Think about it: today we favor practices like version control (makes it easy to recover/compare previous versions of a class), refactoring (which encourages us to modify code to improve design, or as a prelude to introducing new features), and developer testing, which provides a safety net when modifying existing code.

Corbitt answered 27/6, 2009 at 22:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.