Goodbye Getters and Setters or OOP Done Right
Object oriented design is not trivial, and easy to get into a tangle with. Especially if your background was in procedural languages like C or PHP (as it was written in the past). In fact it took me years to understand some very fundamental aspects of OOD, many of which were laid out right at the beginning by luminaries such as Alan Kay, the guy who invented the first OO language Smalltalk. For example, Kay stipulated that “Objects communicate by sending and receiving messages” not by “calling functions”, one of the first misunderstandings when coming from a procedural language.
But that’s not what I’m here to talk about. The point I’d like to hammer home is how one of the most fundamental aspects of OOD is so often overlooked it’s almost tragic: Encapsulation
The whole idea of having concepts represented as “objects” is that their data and behaviour are kept private, in other words “encapsulated” in the object. You can easily fool yourself into thinking that you’re doing OOP if you have classes with private properties, properly “protected” because they are not directly exposed but only accessed via “getters” and “setters”. Not enough is written about how this practice defeats the whole purpose of object oriented programming and encapsulation.
On one hand, one could argue that by having accessors to your private properties ensures that nothing is read or written to the object without having the proper validation or safeguards. But the reality is that in most cases programmers simply add getters and setters to a class’ private properties in order to make them accessible from the outside, and they end up looking something like this:
And why do our classes end up looking like this? Because somewhere we need to so some kind of operation on these objects which requires accessing these properties in order to do some computation like this:
This is the result of imperative thinking, which results in an imperative (and by corollary procedural) style of programming. What we want is a more declarative style.
And at this point I am reminded of an insightful tweet by @matthiasnoback who wrote something along the lines of: Why are you pulling all that data out of the objects only to push it back in again? Whereupon the old adage (often repeated by my ex-colleague and mentor @_md: PUT THE DATA WHERE THE BEHAVIOUR IS! — which is basically encapsulation in a nutshell.
(see also “Tell, Don’t Ask”, I’m not sure who coined this phrase but it goes as far as Sharp, A. “Smalltalk By Example” McGraw-Hill, 1997. at least)
So where do we go from here? How do we apply the concept of encapsulation? Can do we get rid of those getters and setters?
If you may have noticed, both the classes Card and Account are what are known as “anaemic classes”. The don’t do anything in themselves, they are merely containers for data. They lack behaviour, the break encapsulation — they might as well be C structs.
Putting the behaviour where the data is might not seem like a very clear instruction at first glance, but it really is simply a matter of moving the behaviour (computation) into the class where the data required for the calculation is actually held (in order words, putting the behaviour where it really belongs).
How do we apply this adage to the problem at hand?
Lets look at what the code is actually doing (which really isn’t very clear is it?)
On line 9, we’re checking whether our account has sufficient balance to topup the card. Lets give Account a method called hasSuffcientBalanceOf($money). On lines 10–12 we’re crediting our Card with that amount of $money. Lets give the Card a method called creditWith($money). On line 13 we’re debiting our Account with that same amount. Lets give it a method called debit($money).
Wow! Look what happened to our CardCharger class:
Suddenly it becomes clear what this code actually does!
And what’s become of the Card and Account classes? Lo and behold, not only have they shed their getters and setters, they have become much, much simpler:
And of course we can go even further by applying the same logic to the Money class (and of course it too loses its getters):
So, you see, putting the behaviour where the data is goes a long way to not only simplify and remove unnecessary code but preserves encapsulation in the way that nature intended!
Now , it’s true that sometime you do want (or even need) getters. But in 90% of the cases you most certainly don’t. And what’s more, putting some effort into moving the behaviour where it belongs (with its data) can simplify the code a great deal.
In part II of this article I will address how we apply this thinking to some slightly thornier problems using a trick called double-dispatch, which helps to both decoupling, preserving encapsulation and keeping behaviour where it belongs.