So let’s say you have a class or a family of classes and you want to implement some new behavior to the class, how would you go about doing that?
I don’t like inheritance, it leads to tightly coupled code and a nightmare to maintain over time. If you want to change something in the superclass, that affects all of the subclasses. You would have to inspect each subclass to make sure nothing bad happened (or will happen).
Okay, what if we used an interface? It’s better than inheritance, but it’s still not quite what we’re looking for. There’s no code re-use among different behaviours and if you change the interface, you would still need to track down all subclasses that use the interface and make sure the subclass is behaving like you want it to. The point I’m trying to make is that we want our new behaviour to respond to change and be dynamic.
Design principle #1: Favour composition over inheritance. With composition, we are able to dynamically change behaviour at runtime. With inheritance, behaviour was decided at compile time. You can’t change the behaviour at runtime when you use inheritance.
Design principle #2: Identify the aspects of your application that vary and separate them from what stays the same. That way you can alter or extend parts that vary without affecting the parts that are static.
There are a couple of key questions that we must answer.
- Which part of our base class varies? Which part doesn’t vary?
- How can we pull out the behaviour that is prone to change?
- How can we extend this behaviour in the future without having to rewrite existing code?
Stuff’s getting a bit too abstract, so why don’t we imagine a scenario and learn from that. Let’s say we have a base class ‘Engine’. From this base class we can derive as many subclasses as we want. For example we can have the subclass ‘Diesel’ which extends ’Engine’. A typical sports car engine is most likely turbocharged or supercharged. You don’t have to know what these things mean, just know that they represent two different behaviours we want our engine to possess. So let’s create two new classes: ‘Turbocharged’ and ‘Supercharged’. We want the behaviours to be flexible. Maybe our car engine came out of the factory with a turbocharger, but then we decided to swap it out and put in a brand new supercharger. We’ve got our behaviours defined, but we’re not quite ready to implement them. You wouldn’t want the Engine class to be tied to these specific implementations. This leads us to another design principle.
Design principle 3: Program to an abstraction, not an implementation.
What we can do is define the interface ‘Charged’ (which will represent our two different behaviours). The Engine class will be tied to these interfaces, but not a specific implementation. Each Engine subclass will use a specific implementation of this behaviour.
Explanation:
- Charged represents an abstraction of turbocharging and supercharging a car engine
- The classes Turbocharged and Supercharged are implementations of these behaviours
- The Car class contains and instance variable of the Charged interface
- The operation of “charging” an engine is delegated. The Engine base class does not do it itself
- In the constructor of Diesel engine, the charger behaviour is set up.
Wow that’s a long and detailed explanation of our problem and its solution, wouldn’t it be great if this was already formally defined and a specific term existed? Oh wait…
Ladies and gentlemen I would like to introduce you to the Strategy Design Pattern.
Formal definition: “Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”
- Which part of the code varies?
- What part of the code does not vary?
Using composition, how could you isolate the parts that vary?
- Create an interface that represents the variable behaviour
- Compose base class with this variable behavior
- Have base class delegate to this behaviour
Another big advantage to separating the variable behaviour like this is unit testing. The new class can be created and unit tested on its own in complete isolation of the infrastructure that uses it.
It’s good to note that we can use dependency injection to inject the needed algorithm.
Literature
If you want to learn more I highly recommend the book: Head First Design Patterns. It's an amazing and very understandable approach to explaining patterns and design principles.
I also recommend the following website: refactoring-guru and make sure to check out this very good YouTube video explaining the Strategy pattern in practice.
Thank you for your time ❤️. If you have any questions or suggestions, leave a comment down below.