The object-oriented idea often is associated with inheritance and obscure hierarchies. Lots of books and web articles recommend to prefer delegation to inheritance. Do they implicitly recommend to not use object-oriented languages?
In this Blog I want to bring more light into the discussion about "inheritance versus delegation", which I consider unbalanced. Mind that, from a technical point of view, both delegation and inheritance are about code reusage. Nevertheless, from the business perspective, inheritance may represent a determining and deliberate concept that is nice to have also in source code.
(Table of contents generated by JavaScript.)
We are going to implement following application by both inheritance and delegation:
public class Main
{
public static void main(String[] args) {
Dog lupo = new Dog();
lupo.eat("sausage");
lupo.bark();
Cat garfield = new Cat();
garfield.eat("milk");
garfield.miaow();
}
}
Both the dog lupo
and the cat garfield
can eat, but only the dog can bark, and only the cat can miaow. We want to put the reusable eat()
logic into a common class, first using inheritance, then delegation.
Inheritance represents the "IS A" concept:
This is categorization, one of the oldest scientific ways of working, closely related to classification, generalization, abstraction.
class Animal
{
private String eatenFood;
public void eat(String food) {
this.eatenFood = food;
}
public String getEatenFood() {
return eatenFood;
}
}
class Dog extends Animal
{
public void bark() {
System.out.println("Wow Wow");
}
}
class Cat extends Animal
{
public void miaow() {
System.out.println("Miaaaaaow");
}
}
Both Dog
and Cat
inherit from Animal
, and thus can eat()
. A Dog
has the special ability to bark()
, a Cat
the ability to miaow()
. This code is short and concise and contains no duplication of any kind.
Delegation can represent both the "HAS A" and the "IS A" concept:
Doesn't sound very intuitive, but is acceptable from a technical perspective, because inheritance is resolved to delegation by the compiler!
class Animal
{
private String eatenFood;
public void eat(String food) {
this.eatenFood = food;
}
public String getEatenFood() {
return eatenFood;
}
}
class Dog
{
private Animal animal = new Animal();
public void eat(String food) {
animal.eat(food);
}
public String getEatenFood() {
return animal.getEatenFood();
}
public void bark() {
System.out.println("Wow Wow");
}
}
class Cat
{
private Animal animal = new Animal();
public void eat(String food) {
animal.eat(food);
}
public String getEatenFood() {
return animal.getEatenFood();
}
public void miaow() {
System.out.println("Miaaaaaow");
}
}
Both Dog
and Cat
have an Animal
within them, but calls to eat()
and getEatenFood()
have to be forwarded to that delegation object. This is purely technical code that duplicates Animal
method signatures. The specific abilities like bark()
and miaow()
can hardly be seen, because all the delegate methods are public. (Yes, this clutters code!)
Use delegation when having a clear containment hierarchy like "A cat has a tail".
Deriving aLabeledButton
fromHorizontalLayout
just to be able to use the layout methods without having to create a delegate is not worth it. You would expose theHorizontalLayout
API to all callers ofLabeledButton
. Moreover the next release may demand the layout to be switchable toVerticalLayout
at runtime.
Only inherit from a class that you want 100% inside your class. There should not be a single public or protected super-method that doesn't fit to your class.
AStack
withpush(), pop(), peek()
is not expected to inherit fromList
withadd(), remove(), contains()
, as such an API would look absurd for aStack
.
Why extendThread
when you just want some logic to be runnable in a separate thread? Most likely you won't even use the inheritedThread
methods in your logic. Better implementRunnable
and let the caller decide about a thread of its choice.
UI controllers (callback method containers) are a border case. Practice shows that here inheritance leads to very high and incomprehensible hierarchies that end up to be mixins actually.
When you need to reuse more than one class, use delegation, even if one of the super-classes would fit 100% to your class.
When you need to inherit dynamically at runtime, you need to use delegation, because inheritance is compile-time bound.
Use inheritance in any other case, especially when having a clear inheritance hierarchy like "A cat is a mammal". Inheritance is a much better reusage concept than delegation.
We still live in times of manually written source code, and very frequently the source code is the only representation of a business logic that came from experts that are long gone and left no documentation ("we are agile"). Thus we need well readable und quickly understandable source code without duplications, easily maintainable, because maintenance makes up 70% of software production efforts.
Once again I want to remark that inheritance is resolved to delegation at compile time, so why implement delegation when the compiler can do it for you, without typos and code duplication?
Mind that any such list always is driven by the point of view: A compiler engineer may give other pros and cons than a database expert. I am a business developer.
Inheritance is said to be a too intimate relationship. E.g. you could modify non-final protected fields of a super-class, causing unexpected behavior. You could override a method and forget to call super, or do it in a way the super-class did not expect.
To avoid that, do the following in any super-class:
private
, and as many methods as possible private
, package-visible, or at least protected
(minimize access modifiers)protected
fields final
protected
fields private
and a add a protected final
getter for it (when necessary), try to avoid the setterprivate
field has protected
getter and setter, the field must not be accessed directly (in its class), only by its getter and setter, as they could be overwritten when being non-final private
methods called from constructor final
, see hereprivate
methods to uncover illegal object statesprivate
methods final
that are not explicitly meant to be overwritten, or that may endanger the object's state when overwritten Should be self-evident, but belongs here: public
fields always must be final
(immutable)!
We need both of them, inheritance AND delegation!
Obviously delegation is the more flexible concept. But with more freedom comes less security, meaning the code is hard to maintain when having those big delegation sections in it.
Absence of abstraction means low quality of both specification and software. Code without abstractions will contain many duplications due to missing code reusage.
Respect business logic and its concepts. When the specification comes with an "IS A" relation, then use inheritance. When it comes with a "HAS A" relation, use delegation. When it comes with neither, tell them to introduce it :-)
ɔ⃝ Fritz Ritzberger, 2020-12-20