JavaScript Composition vs Inheritance

September 23, 2019

Previously, we looked at how to accomplish
inheritance in JavaScript using both ES5 as well as ES6. In our example, we abstracted the common features
amongst every animal name, energy, eat, sleep, and play to an animal-based class as we see
here, then whenever we wanted to create an individual type of animal whether it was a
dog or a cat, etc., we created a subclass for that type, seen here. So without the code, we can visualize the
structure like this, our base class was animal, and then dog and cat were subclasses. This work well as it allowed us to minimize
code duplication and maximize code reuse. But, let’s take this a step further and pretend
we’re building software for Farm Fantasy, a massive online MMO role-playing game where
you do the exact same thing a farmer does except, you know, online and you pay for it So now that we’re creating an MMO, we’re going
to need to have users. We can update our class structure now to look
something like this. This structure is textbook inheritance. Sadly, unlike in the classroom, real software
development isn’t always so predictable. Let’s say six months after building out our
initial class structure, our project manager decided we need to change some things around. Users love the app and the ability to pay
to be a pretend farmer, but they want a more real-life experience. Right now, only instances of animal have the
ability to eat, sleep, and play, the users are demanding that they also have those same
features. All right, so no worries we can just adjust
our class structure a little bit. Well, how would we go about doing this? Again, we want users now to be able to eat,
sleep, and play as well. But those functionalities are kind of encompassed
and encapsulated inside of the animal class. So I guess one thing we could do is we could
abstract the common properties to another parent class, and have one more step of inheritance. Now, technically that works but it’s pretty
fragile. There’s even a name for this kind of anti-pattern,
it’s called the god object. And just like that, we see the biggest weakness
with inheritance. With inheritance, you structure your classes
around what they are, a user, an animal, a dog, a cat, all of those words encapsulate
a meaning centered around what those things are. The problem with that is a user today will
probably be different than a user in six months. Inheritance makes us turn a blind eye to the
inevitable fact that our class structure will most likely change in the future. And when it does, our tightly coupled inheritance
structure is going to crumble. The essence of this problem could be wrapped
up in this quote by the creator of Erlang. “The problem with object-oriented languages
is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a
gorilla holding the banana and the entire jungle.” So if inheritance is such a problem, how do
we get the same functionality while minimizing the downsides? Rather than thinking in terms of what things
are, what if we think in terms of what things do? Let’s take a dog, for example. A dog is a sleeper, eater, player, and a barker. A cat is a sleeper, eater, player, and a meower. A user would then be a sleeper, eater, player,
adopter, and friender. So now let’s transform all of those verbs
into functions that look something like this. So you might see where this is going, instead
of having these methods defined and then coupled to a particular class, if we abstract them
into their own functions, we can now compose them together with any type that needs them. So let’s look at one of our previous methods
again. Notice that eat logs to the console that increases
the energy property on the instance by the amount argument. Now, the question we need to answer is how
can we operate on a specific instance from a one-off function? What if we just pass it in when we invoke
the function? Something like this, where instead of operating
on the instance, again, we pass in the state of the instance when we invoke the specific
function. And via closures, anytime the eat method is
called, we then just modify the state that was passed in. So we can follow this pattern with each one
of our functions. So we have a sleeper, a player, barker, meower,
adopter, and friender. So now, whenever a dog, cat or user needs
to add the ability to do any of those functions, they merge the object they get from one of
the functions onto their own object. So let’s see what this looks like. Let’s start with dog. Earlier, we said that dog by definition is
a sleeper, an eater, a player, and a barker. So we create our dog here, has a name, energy,
and a breed. And then, what we return is the dog along
with all of these invocations merged into it. Again, all of these invocations are returning
methods that when they’re invoked will modify the specific dog because that’s what we are
passing in here. So again, inside of dog, we create the instance
using just a plain old JavaScript object, then we use object.assign to merge the dog
state with all of the methods a dog should have. Each defined by what a dog does, not what
it is. So now the question is, how do we go about
creating a cat class? Well, earlier we defined a cat as a sleeper,
eater, player and meower, so we followed the same logic we did with the dog but now we
have eater, sleeper, player, and meower. What about a user? So earlier, we ran into issues when we needed
to refactor our class structure so that users could also sleep, eat, and play. But now that we’ve decoupled our functions
from the class hierarchy, this is pretty trivial to do. A user has an email, username, pets, and friends,
and it has the ability to eat, sleep, play, adopt or friend. Now, to really test our theory
what if we wanted to give all dogs the ability to add friends as well? This wasn’t in our initial requirement but
with composition, it’s pretty straightforward. Dogs can now eat, sleep, play, bark, and friend. By favoring composition over inheritance and
thinking in terms of what things do rather than what things are, you free yourself of
fragile and tightly coupled inheritance structures.

Leave a Reply

Your email address will not be published. Required fields are marked *