Subscribe to PHP Freaks RSS

Final classes by default, why?

syndicated from planet-php.net on September 11, 2018

I recently wrote about when to add an interface to a class. After explaining good reasons for adding an interface, I claim that if none of those reasons apply in your situation, you should just use a class and declare it "final".



PHP 5 introduces the final keyword, which prevents child classes from overriding a method by prefixing the definition with final. If the class itself is being defined final then it cannot be extended.



— PHP Manual, "Final Keyword"



An illustration of the final syntax for class declarations:



final class CanNotBeExtended
{
    // ...
}

class CanStillBeExtended { // ... }

class Subclass extends CanStillBeExtended { // override methods here }

// Produces a Fatal error:

class Subclass extends CanNotBeExtended { // ... }


For a couple of years now I've been using the final keyword everywhere (thanks to Marco Pivetta for getting me on track!). When I see a class that's not final, it feels to me like it's a very vulnerable class. Its internals are out in the open; people can do with it what they want, not only what its creator has imagined.



Still, I also remember my initial resistance to adding final to every class definition, and I often have to defend myself during workshops, so I thought it would help if I explained all about it here.



The alternative: non-final classes



Omitting the final keyword is the standard for many developers, who reason that they should allow others to reuse the class and change just a part of its behavior by extending it. At first this may seem like a very considerate thing to do, but there are several downsides to it.



You are the initial designer of the class, and you had one particular use case in mind when you created it. Using the class for something else by overriding part of its behavior may jeopardize its integrity. The state of the extended object may become unpredictable or inconsistent with the state of the original object. Or the behavior may change in such a way that the subclass can no longer be considered a proper substitute for the base class.



Furthermore, the developer who creates a subclass of your class to override its behavior, probably doesn't need all of the internals of the parent class. Still it inherits those internals (data structures, private methods), and will soon arrive at the point that it has to work around them.



In terms of future development there's another issue: the class that has now been subclassed, still has a life on its own. Its clients may have different needs over time, so changes will be made to it. These changes may affect the subclasses too, since they likely rely on a particular implementation detail of the parent class. When this detail changes, the subclass will break.



From all these issues we have to conclude that by allowing subclassing, we will end up with an undesirable situation. The future of the subclass and the parent class will become intertwined. Refactoring the parent class will be quite hard, because who knows which classes are relying on a particular implementation detail. And since the subclass doesn't need all of the parent class's data and behaviors, they may act like they are the same kind of thing, but in reality, they aren't.



Replacing is better than overriding



So changing the behavior of a class shouldn't be done by subclassing that class and overriding methods. But what else can we do? In most cases, actually modifying the code of the original class isn't possible. Either because the class is used and relied on elsewhere in the project, or it's not physically an option to modify its code because it's part of a vendor package.



I'd even say that changing code shouldn't be the preferred way of changing behavior in the first place. If you can, change the behavior of an object by reconfiguring it with, that is, by swapping out constructor arguments, you should do it.



This reminds us of the Dependency inversion principle, according to which we should depend on abstractions, and the Open/closed principle, which helps us design objects to be reconfigurable, without touching its code.



What about the Template Method pattern?



There's an alternative approach for changing the behavior of a class. It's a behavioral pattern described in the classic "Design Patterns: Elements of Reusable Object-Oriented Software". The pattern is called "T

Truncated by Planet PHP, read more at the original (another 9681 bytes)