37

I am refactoring a PHP OOP legacy website.

I am so tempted to start using 'final' on classes to "make it explicit that the class is currently not extended by anything". This might save lots of time if I come to a class and I am wondering if I can rename/delete/modify a protected property or method. If I really want to extend a class I can just remove the final keyword to unlock it for extending.

I.e If I come to a class that has no child classes I can record that knowledge by marking the class a final. The next time I come to it I would not have to re-search the codebase to see if it has children. Thus saving time during refactorings.

It all seems like a sensible time saving idea.... but I have often read that classes should only be made 'final' on rare/special occasions.

Maybe it screws up Mock object creation or has other side effects that I am not thinking of.

What am I missing?

JW01
  • 3,569

7 Answers7

72

I have often read that classes should only be made 'final' on rare/special occasions.

Whoever wrote that is wrong. Use final liberally, there’s nothing wrong with that. It documents that a class wasn’t designed with inheritance in mind, and this is usually true for all classes by default: designing a class that can be meaningfully inherited from takes more than just removing a final specifier; it takes a lot of care.

So using final by default is by no means bad. In fact, amongst OOP experts it’s widely agreed that final should be the default, e.g. Jon Skeet:

Classes should be sealed by default in C#

Or Joshua Bloch:

Design and document for inheritance or else prohibit it [Effective Java, 3rd Ed, Item 19]

Or Scott Meyers [More Effective C++, Item 33].

Which is why modern OO langauges such as Kotlin have final-by-default classes.

You wrote:

Maybe it screws up Mock object creation …

And this is indeed a caveat, but you can always recourse to interfaces if you need to mock your classes. This is certainly superior to making all classes open to inheritance just for the purpose of mocking.

8

If you want to leave a note to yourself that a class has no sub-classes, then by all means do so and use a comment, thats what they are for. The "final" keyword is not a comment, and using language keywords just to signal something to you (and only you would ever know what it means) is a bad idea.

edited by original author to add: I completely disagree with this now. I cannot even create a model of my mental state 11 years ago that would explain why I would say this. I think this answer, and my comments defending it below, are ridiculous. The accepted answer is right.

Jeremy
  • 4,597
6

There is a nice article about "When to declare classes final". A few quotes from it:

TL;DR: Make your classes always final, if they implement an interface, and no other public methods are defined

Why do I have to use final?

  1. Preventing massive inheritance chain of doom
  2. Encouraging composition
  3. Force the developer to think about user public API
  4. Force the developer to shrink an object's public API
  5. A final class can always be made extensible
  6. extends breaks encapsulation
  7. You don't need that flexibility
  8. You are free to change the code

When to avoid final:

Final classes only work effectively under following assumptions:

  1. There is an abstraction (interface) that the final class implements
  2. All of the public API of the final class is part of that interface

If one of these two pre-conditions is missing, then you will likely reach a point in time when you will make the class extensible, as your code is not truly relying on abstractions.

P.S. Thanks to @ocramius for great reading!

Nikita U.
  • 173
  • 2
  • 7
5

"final" for a class means: You want a subclass? Go ahead, delete the "final", subclass as much as you like, but don't complain to me if it doesn't work. You're on your own.

When a class can be subclassed, it's behavior that others rely upon, must be described in abstract terms that subclasses obey. Callers must be written to expect some variability. Documentation must be written carefully; you can't tell people "look at the source code" because the source code isn't there yet. That's all effort. If I don't expect that a class is subclassed, it's unnecessary effort. "final" clearly says that this effort hasn't been made and gives fair warning.

gnasher729
  • 49,096
1

Using 'final' takes away freedom of others that want to use your code.

If the code you write is only for you and will never be released to the public or a customer then you can do with your code what you want, of course. Otherwise, you prevent others from building upon your code. Too often have I had to work with an API that would have been easy to extend for my needs, but then I was hindered by 'final'.

Also, there is often code that should better not be made private, but protected. Sure, private means "encapsulation" and hide things considered to be implementation details. But as an API programmer I might as well document the fact that method xyz is considered to be implementation detail and, thus, may be changed/deleted in future version. So everyone who will rely on such code in spite of the warning is doing it on his own risk. But he can actually do it and reuse (hopefully already tested) code and come up faster with a solution.

Of course, if the API implementation is open source one can just remove the 'final' or make methods 'protected', but than you have changed the code and need to track your changes in form of patches.

However, if the implementation is closed source you are left behind with finding a workaround or, in the worst case, with switching to another API with less restrictions regarding possibilities for customization/extension.

Note that I don't find 'final' or 'private' are evil, but I think they are just used too often because the programmer didn't think about his code in terms of code reuse and extension.

Dan Lugg
  • 113
Jonny Dee
  • 947
0

I would like to add some nuance to the answer provided by Konrad Rudolph. While his response is certainly true in most contexts, it does not mention the situation where the code may be used as a dependency of another project.

The project that depends on this code may need to modify the behavior of the final class for a use case that was not anticipated by the original creator of the class (it is impossible to anticipate all situations), and marking the class as final could be quite problematic.

Let's imagine a scenario: I have coded a Symfony library that allows sending Batman-themed emails. The template is customizable, as it is often the case with most Symfony libraries because of its native template overriding mechanism.

I have created a service that contains a method sendBatmanEmail(BatmanEmailDto $emailDto) To use this service, the caller must initialize a BatmanEmailDto, which offers two setters setSubject and setText. The sendBatmanEmail method does its job (at least 300 lines of code!), then passes the DTO to the template for rendering. The DTO is marked final.

In most cases, it works fine with a subject and a text! Some slight modifications are possible via the Symfony template overriding engine, which is the icing on the cake.

But if tomorrow a project wants to have two distinct texts in the template, or add an image, manipulate a richer entity, etc... The use of the library will be completely questioned since it will be impossible to extend the BatmanEmailDto and pass more data to the template. The sendBatmanEmail method will need to be rewritten entirely (or maybe entirely copy/pasted? good luck for the maintenance!..)

All of this is to say that if you are developing a library and not a final project, please avoid using final classes. It looks nice on paper, but for future dependencies of your project, it rarely seems like a good idea.

And I forgot to mention if you want to replace some class by dependency injection configuration, which is a very common type of usage in Magento for example (and is very maintainable if used with caution when and only when it is useful btw)

PS: if some developer is not clever enough to understand that your class is not made to be extended, he will probably not be clever enough to NOT remove your final keyword also.

-1

One thing you may not have thought of is the fact that ANY change of a class means that it has to undergo new QA-testing.

Do not mark things as final unless you really, really mean it.