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.)
Code Examples
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
Inheritance represents the "IS A" concept:
- A cat is an animal
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
Delegation can represent both the "HAS A" and the "IS A" concept:
- A cat has an animal (inside, thus it is an animal)
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!)
When to Use What
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?
Advantages and Disadvantages
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
Advantages
- You effortlessly get whatever the super-class provides, without any code duplication
- You could fix bugs or customize behavior in a class you have no editing-access to (external frameworks), when inheriting from it and overriding methods
- No repetitive delegation code clutters the implementation
Disadvantages
- You can not inherit at runtime, binding to a super-class at runtime normally is not supported
- Mostly you can not reuse several super-classes at the same time; some languages provide multiple inheritance (→ mixins), but you are going to be ambiguous, misleading and complex when you use that feature
- Inheritance is a too intimate relationship, meaning the derived class can bring down the super-class by overrides or use of protected fields that are meant to be internal-only; that can be avoided only by accurate and well written access modifiers and field wrappers in the super-class, see chapter below
Delegation
Advantages
- You can lazily load the delegate
- You can bind the super-class delegate at runtime
- You can simulate multiple inheritance
Disadvantages
- Delegation is code duplication
- Mostly the delegate needs an interface, so that all delegators can also implement it and forward to the delegate, which gives an additional interface and lots of purely technical method-forwarding in all delegators
- When the delegate interface changes, you need to change both the delegate and all delegators
- Without a delegate interface you can be sure that you will forget to change all delegators when the delegate changes!
How to Avoid Inheritance Break Encapsulation
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:
- Try to make all fields
private
, and as many methods as possibleprivate
, package-visible, or at leastprotected
(minimize access modifiers) - Make immutable
protected
fieldsfinal
- Make mutable
protected
fieldsprivate
and a add aprotected final
getter for it (when necessary), try to avoid the setter - When a
private
field hasprotected
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 - Make all non-
private
methods called from constructorfinal
, see here - Add assertions to all non-
private
methods to uncover illegal object states - Make all non-
private
methodsfinal
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)!
Conclusion
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 :-)
Keine Kommentare:
Kommentar veröffentlichen