Inhalte

Common Practices: Inheritance

Table of Contents

Introduction

Inheritance apparently takes a lot of work off our shoulders, but because of the dependencies that inheritance creates, it takes a fair amount of attention to decide when inheritance makes sense and when it should be avoided.

On the one hand, inheritance makes it effortless and cheap to make changes that affect all subtypes, which is wonderful when it comes to a bug fix. If something needs to be extended, this is also easy with inheritance (hooray! We work according to the open-close principle!). On the other hand, it cannot be denied that this is only bought by a very strong dependency. Each subtype is directly related to its parent type. Another aspect is that it is not always clear what changes will come. Here it can suddenly become expensive, if an entire class structure must be adapted.

If you don't know the future, then it is often easier to use the I-have or I-can relationship instead of the I-am-one relationship. This costs more implementation time because one must create many interfaces and distribute the responsibilities partly on several classes. However, these relations are usually cheaper with an uncertain future.

To be or not to be a subtype

In inheritance, the following must always be satisfied to allow the replacement of objects:
If for the class Foo there is the specialization FooBar, programs and their behavior using Foo must remain unchanged when Foo is replaced by FooBar. Conversely, this means that FooBar is only a subtype of Foo if the replacement of Foo by FooBar does not cause any changes in the behavior of the users of Foo.

Don’t waste your time

If you have understood everything, or if you are familiar with Barbara Liskov's substitution principle, then skip the rest of the chapter. Everything is clear then proceed with https://ekvip.atlassian.net/wiki/x/B4AcYg

Explanation using a bad example

For a better understanding, here is an example where the required principle is violated:

We have a rectangle and a square, in mathematics, the square is a specialized form of the rectangle (every square is also a rectangle).

Rectangle class
FUNCTION_BLOCK Rectangle
VAR
	sideLenghtA		:LREAL;
	sideLenghtB		:LREAL;
END_VAR
GET PROPERTY a :LREAL


a := THIS^.sideLenghtA;
SET PROPERTY a :LREAL


THIS^.sideLenghtA := a;
GET PROPERTY b :LREAL


b := THIS^.sideLenghtB;
SET PROPERTY b :LREAL


THIS^.sideLenghtB := b;
METHOD getArea  :LREAL
getArea := THIS^.sideLenghtA*THIS^.sideLenghtB;
Square class
FUNCTION_BLOCK Square EXTENDS Rectangle
SET PROPERTY a :LREAL


THIS^.sideLenghtB := 
THIS^.sideLenghtA := 
a;
SET PROPERTY b :LREAL


THIS^.a := b;
Client of Rectangle
METHOD doSomething
VAR_INPUT
	rectangle	:REFERENCE TO Rectangle;
END_VAR
VAR
	area		:LREAL;
END_VAR
//...
rectangle.a := 1337;
rectangle.b := 42;
area := rectangle.getArea();
THIS^.assert(area = 56154);
//...

In the example, you can see very well that square must not be a subtype of rectangle. Because in the rectangle, the sides a and b can be changed independently of each other, whereas in the square the side length always changes together. In the example, the client-object of rectangle would change its behavior if it got a square object instead of a rectangle object. This violates the principle that a subtype is only a subtype if replacing the base type with a subtype does not change the behavior.