We use access modifiers to hide certain properties or methods — public, protected, private. Final is used to disallow children. But why would we hide something or disallow anything? Why to do such things?
We have to often implement new features or modify existing ones. This results in extending current code, extending parameters of methods and ultimately to the code refactoring.
Let’s assume following code — it deals with Orders and we allow applying percentage discount to the whole order. This percentage discount is applied to all order items.
getDiscount() method is public because it seems nice to provide this functionality to programmers, so they don’t have to repeat it.
As we can see, the price is represented as an integer.
We don’t care about the Item in this article, but if You cannot think about the code without the class and return types, here is the Item
We have to support multiple currencies in our system.
After a short research, we realize there is a good library brick/money that deals with currencies and even with decimal numbers correctly. So we are going to use it instead of integers.
Obviously, the Item have to work with the Money class
The Order class now looks like
So far good. We implemented the new feature.
Changes in the System
We changed the class Order, specifically the class interface
getDiscount(int $price, int $percent): intto
getDiscount(Money $price, int $percent): Money
But this isn’t the end of changes. We have to adjust also
- All internal usages of this method (done, by the change in the Item)
- All outside usages of this method — because it is public
- Interface of all descendants — because the might be descendants
- All usages in descendants— because descendant might use it
Quite a lot of work. And why? Because we wanted to allow our colleagues to use the
getDiscount(), as we assumed it might be practical. Really a lots of work just because of an assumption, not because of a real need.
Doing Less Work
Requirements changes, therefore the code changes as well, we refactor the code by new needs, and that’s our job.
Let’s protect our code, so we have to do less changes.
We protect the code by
Let’s repeat how much work do we have to do when we change the interface of the method
- All internal usages of this method (yes, there is no way around)
- A̶l̶l̶ ̶o̶u̶t̶s̶i̶d̶e̶ ̶u̶s̶a̶g̶e̶s̶ ̶o̶f̶ ̶t̶h̶i̶s̶ ̶m̶e̶t̶h̶o̶d̶ — no, it’s private
- I̶n̶t̶e̶r̶f̶a̶c̶e̶ ̶o̶f̶ ̶a̶l̶l̶ ̶d̶e̶s̶c̶e̶n̶d̶a̶n̶t̶s̶ — no, it’s final
- A̶l̶l̶ ̶u̶s̶a̶g̶e̶s̶ ̶i̶n̶ ̶d̶e̶s̶c̶e̶n̶d̶a̶n̶t̶s̶ — no, it’s private and final
If we protect internals of classes, we will become free to change internals, even implement new features, without doing a lots of effort.
Private and Final
If we accept
private as a default state, we unlock the ability of refactoring and introducing new features without too much pain. Especially when working in a team where everyone uses everything.
Use public, protected when have a Reason
When a teammate need to create descendant, use private method in descendant, use private method from outside, no problem. The teammate changes the default state. Removes the final, softens the visibility from private to protected or to public.
But we do it only when we have a reason for doing so. And such reason have to be mentioned in the version control system (eg. GIT), so every teammate is aware why did someone changed the default state.
final, and You’ll do less work.