Key Takeaways
- “Favor composition over inheritance” is a common piece of advice in software development, as it allows for more flexibility and less coupling in the code. It is particularly applicable to Ruby code, where inheritance can lead to tightly coupled classes that are difficult to test and separate.
- Inheritance can often lead to problems midway through a project or when requirements change. Instead of modelling relationships as ‘is-a’, they can often be more effectively expressed as ‘has-a’, which is more flexible and can be achieved through composition.
- Composition works by moving shared features into a class of their own. Ruby also offers the use of Modules to achieve similar effects. This allows for more efficient code sharing and avoids the issues that can arise with inheritance.
- Inheritance can lead to ‘white box’ classes where all methods are public and the internal state of the class can be modified externally. This can lead to issues with visibility and overriding methods from parent classes. Composition, on the other hand, promotes ‘black box’ implementations where classes serve a single purpose, leading to more manageable and maintainable code.
So What’s Wrong with Inheritance
I’m going to assume, for the purposes of this article, that you’re all as a bad a programmer as I am. I’m also going to assume you make similar mistakes and find yourself in similar tangles as a project goes from ‘this-is-going-to-be-awesome’ green field development to ‘oh-no-why-did-i-do-that’ maintenance mode. So, if this reads like a bit of a confession piece, it’s because I’ve made all these mistakes so you don’t have to (again).Modelling the Real World
Many of my headaches in development have come from a misunderstanding of what inheritance is and why you should use it (and indeed, I’m sure I’m about to offer an incorrect definition here). A beginner programmer – be it in Ruby, Java or whatever – is tempted to look at inheritance and think “neat, I makeFoo
inherit from Bar
and I get a all the features of Bar
for free.” And it’s true, Foo < Bar
will do just that. What that doesn’t quite capture is, in anything but the smallest of programs, saying ‘Foo is a Bar’ only holds for a limited time.
Eventually, Foo and Bar will diverge enough that the correct statement becomes more like ‘Foo shares some features of Bar’. By now of course, it’s too late and you’re 3 months into a project and stuck with two tightly coupled classes which are hard to test and difficult to separate.
Often, inheritance issues manifest themselves midway through a project or if requirements change (and they will). This is because a relationship which looks like it can only be modelled as a ‘is-a’ relationship can be expressed as a ‘has-a’ relationship – and a ‘has-a’ relationship is a whole lot more flexible.
An Example
Let’s look an example. Say we’re trying to model the world of development. We’ve gotDevelopers
and Testers
. You could reasonably expect any beginner text box to come up with something like this for the class structure.
[gist id=’3932446′]
Perhaps every employee has a @salary
and a Tester
has a test
method, while a Developer
has a make_bugs
method. Now, we need to add another class, the friendly Intern
. People of this type, the spec says, are able to make_bugs
and test
. They also don’t take a @salary
– being effectively slave labour. An Intern
is almost an employee, almost a Developer
and almost a Tester
and there isn’t a clear place they can fit into the class hierarchy.
In this instance, we start off by saying that a Developer
is-a Employee
– which was true for a time, but now we’ve found that a Developer
‘s behaviour is only sometimes a subtype of an Employee
‘a behaviour. We’ve got two options here. We can change our expression of the class so a Developer
has some sort of Employment
– effectively turning the method of payment into a has-a relationship. Alternatively, we could turn the task performed by a Developer
into a has-a relationship. This is where composition comes in.
Composition to the Rescue
Traditional composition, which works pretty much the same way in pretty much every OO language, involves moving those features that we want to share into a class in their own right. So, we’d have something like. [gist id=’3932429′] Ruby also provides a second way to get much the same effect: Modules. Using modules we can inject this bug producing functionality into anyPerson
class by simply writing a mixin.
[gist id=’3932444′]
We can do the same for the testing functionality. Now our Intern
can just be a Person
who has-a ability to code and test.
As we’ve seen here, inheritance isn’t the only method of code sharing available to you and, in many cases, is not the best choice. Good use of inheritance should focus on the representing the ‘real world’ relationships between objects and not on the grunt work of code sharing.
White Boxes
‘White box’ and it’s twin ‘Black box’ are terms used in a variety of fields and mean a variety of different things. When we talk about box colour in terms of code re-use, specifically inheritance, we’re really talking about visibility. A white box class in which all it’s methods are public (ignoring the fact that in Ruby we can see the unseeable with a sneaky call tosend
) – that is to say, we can often modify the data in the object whether or not we’re doing the operation inside the class or externally.
A black box does not allow this kind of access to it’s internal state. It provides a defined set of input methods and a defined set of output. What happens in the middle shouldn’t concern us. In strongly typed languages these inputs and output are usually codified using something like Java’s Interface
In Ruby, unlike Java, there is no way to protect methods in a parent class from being used in a subclass. This means that each subclass has complete visibility of it’s parent class. This, in turn, causes us to care about a given classes ancestry whenever we use it.
Because we’ve got this visibility, we’ve also got a great temptation to override and manipulate methods from our parent. This is fine in most, if not all cases, but it does mean that we now have a contract between the implementation of the parent and the implementation of the child. We can now no longer change one without changing the other.
Wrap Up
Think about your unit tests – specifically your ActiveRecord unit tests. ActiveRecord shares it’s features through the inheritance ofActiveRecord::Base
. Chances are, unless you used something like Mocha to great effect, that significant changes to ActiveRecord would break your test (let alone your code). This is a great example of where inheritance is, perhaps, not the most appropriate choice. Your User
model isn’t a type of ActiveRecord::Base
and shouldn’t pretend to be – and there’s not much point testing the saving of a record to a testing database when you can be pretty certain that when you hand over to active record you’ll be running well tested code anyway.
So, keep your User
model inheriting from active record – but implement key features which need to be shared or inherited from as small isolated classes or modules which.The business rules about a User
should’t be intertwined with the messy work of munging sql statement together.
Preferring composition over inheritance as your “go to” way of sharing code will force you to structure your application in such a way as to produce more classes (or modules) which serve a single purpose. In essence, you’ll be swapping a large whitebox implementation for many blackbox implementations.
As always, I’ll be keeping an eye on the comments and if I’ve been unclear or, indeed, wrong about anything I’d love to hear about it. Have you recently started using composition more thoroughly? Do you disagree that inheritance should not be the preferred method of code sharing? Can you think of any good examples to illustrate the whole dilemma?
Frequently Asked Questions (FAQs) about Composition and Inheritance
What is the main difference between composition and inheritance?
The primary difference between composition and inheritance lies in their use and functionality. Inheritance is a mechanism that allows one class to acquire the properties and behaviors of another class. It creates a parent-child relationship between two classes, allowing for code reusability. On the other hand, composition is a design technique in object-oriented programming where a class is composed of other classes. It allows for a more flexible design as it enables a class to change its behavior dynamically.
Why is composition often favored over inheritance?
Composition is often favored over inheritance due to its flexibility and less coupling. With composition, you can control the visibility of other objects, and it’s easier to change the class behavior at runtime. It also promotes code reusability and modularity. In contrast, inheritance can lead to a high level of coupling, making the code harder to maintain and modify.
Can you provide an example of composition?
Sure, let’s consider a simple example. Suppose we have a class ‘Car’ and another class ‘Engine’. A car has an engine, so we can say the ‘Car’ class is composed of the ‘Engine’ class. Here’s how it might look in code:class Engine {
// Engine properties and methods
}
class Car {
// Car properties and methods
private Engine engine; // Car has an engine
}
When should I use inheritance instead of composition?
Inheritance should be used when there is a clear, hierarchical relationship between classes, and you want to leverage polymorphism. If the classes share common behaviors and you want to avoid code duplication, inheritance can be a good choice. However, it’s important to avoid deep inheritance hierarchies as they can become difficult to maintain and understand.
What is the “has-a” relationship in composition?
The “has-a” relationship in composition refers to the way one class contains an instance of another class. For example, in the ‘Car’ and ‘Engine’ example above, a ‘Car’ has an ‘Engine’. This relationship is a key aspect of composition and is a way to represent real-world relationships in code.
What is the “is-a” relationship in inheritance?
The “is-a” relationship in inheritance refers to the way one class is a subtype of another class. For example, if we have a ‘Vehicle’ class and a ‘Car’ class, and ‘Car’ inherits from ‘Vehicle’, we can say that a ‘Car’ is a ‘Vehicle’. This relationship is a fundamental aspect of inheritance.
Can composition and inheritance be used together?
Yes, composition and inheritance can be used together in a design. They are not mutually exclusive concepts. In fact, using them together can often lead to a more flexible and maintainable design. The key is to use each where it makes the most sense in your design.
What is multiple inheritance and how does it relate to composition?
Multiple inheritance is a feature of some programming languages where a class can inherit from more than one superclass. However, it can lead to a lot of confusion and complexity. Composition is often used as a simpler and more flexible alternative to multiple inheritance.
How does composition help in code reusability?
Composition promotes code reusability by allowing new functionality to be easily added with new classes. A class can use the functionality of the classes it’s composed of, without needing to inherit from them. This means that you can reuse existing classes to create complex functionality without duplicating code.
What is the Liskov Substitution Principle and how does it relate to inheritance?
The Liskov Substitution Principle (LSP) is a principle of object-oriented design that states that if a program is using a base class, it should be able to use any of its subclasses without the program knowing it. This principle is closely related to inheritance, as it guides the design of the inheritance hierarchy to ensure that subclasses are truly substitutable for their base classes.
Daniel Cooper is a web developer from Brighton, UK