Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

46
Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures

Transcript of Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Page 1: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Inheritance 2

Ranga RodrigoBased on Mark Priestley's Lectures

Page 2: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Eiffel supports multiple inheritance, unlike Java.

This simply means that more than one class can be listed in the inherit clause of a class definition.

A potential problem in this situation is that the parent classes might contain methods with the same name: this situation, which often occurs with constructors, can be resolved using renaming:

Page 3: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MOTHERfeature make is ...end

class FATHERfeature make is ...end

class CHILDinherit MOTHER rename make as make_mother end

FATHER rename make as make_father endend

Two parents

Multipleinheritance

andrenaming

Page 4: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance

This is the situation where a class inherits the same class twice.

A common way of generating this situation is when a class inherits from two classes which themselves share a superclass.

Suppose that in parallel to the audible counter discussed above, a counter which flashed a light when incremented had been built for the hard of hearing:

Page 5: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class VISIBLE_COUNTER

inherit COUNTER redefine increment end

create make

feature

increment is do Precursor io.put_string("Flash!%N") endend

VISIBLE_COUNTER

Inheritsfrom

COUNTER

Page 6: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class AUDIBLE_COUNTER

inherit COUNTER redefine increment end

create make

feature increment is do value := value + 1 io.put_string("Beep%N") end

end

AUDIBLE_COUNTER

Inheritsfrom

COUNTER

Page 7: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance

Somebody might now have the idea of developing a multimedia counter which would both beep and flash when incremented.

This is naturally thought of as a specialization of both the audible and visible counters:

class MULTIMEDIA_COUNTER

inherit AUDIBLE_COUNTER VISIBLE_COUNTER...end

Inheritsfrom

AUDIBLE_COUNTERVISIBLE_COUNTER

Page 8: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance

Both AUDIBLE_COUNTER and VISIBLE_COUNTER inherit from COUNTER, however, so the counter class has been inherited twice by MULTIMEDIA_COUNTER. This is known as repeated inheritance.

Page 9: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance

When the Eiffel compiler detects repeated inheritance, it arranges things so that only one copy of the features in the repeated class is inherited by the new class.

In this case, therefore, there would only be one feature called value in the multimedia counter class. In most cases, this is what is required: it would be very difficult to write a safe counter class if it had two value attributes which were updated by different inherited methods.

Page 10: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance

However, the MULTIMEDIA_COUNTER class inherits two methods called increment, one from each of its superclasses.

It needs to define its own method which combines these, emitting both a beep and a flash when it is called.

This can be achieved in Eiffel as follows:

Page 11: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MULTIMEDIA_COUNTER

inherit AUDIBLE_COUNTER redefine increment end

VISIBLE_COUNTER redefine increment end

feature

increment is do ... end

end

MULTIMEDIA_COUNTER

Inheritsfrom

COUNTER

Page 12: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance: Overriding

By stating that both the inherited features are being overridden, the MULTIMEDIA_COUNTER class is declared to have only one feature with that name, and dynamic binding will be enabled to work correctly.

However, actually writing the new version of increment is not quite so straightforward. It would be nice if we could simply combine the two inherited methods by calling them using the precursor construct:

Page 13: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MULTIMEDIA_COUNTER...feature

increment is do Precursor { AUDIBLE_COUNTER } Precursor { VISIBLE_COUNTER } end

end

MULTIMEDIA_COUNTER

Page 14: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance: Overriding

However, this will result in the value feature being incremented twice every time the increment routine is called.

The problem is that the increment routines in the audible and visible counter classes combine two aspects of functionality, and when redefining the feature in the multimedia counter class they need to be separated.

The best that can be done is something like the following:

Page 15: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MULTIMEDIA_COUNTERinherit AUDIBLE_COUNTER redefine increment end VISIBLE_COUNTER redefine increment end COUNTER redefine increment endfeature increment is do Precursor { COUNTER } io.put_string("Beep!") io.put_string("Flash!") endend

MULTIMEDIA_COUNTER

Page 16: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance: Overriding

This involves repeating the code for beeping and flashing, but manages to reuse the original increment methods from the basic counter class.

This design could be improved by defining methods do_beep and do_flash in the intermediate classes.

Page 17: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Repeated Inheritance: Overriding

An alternative, and perhaps better solution, to this problem involves refactoring the code in all the classes in the hierarchy.

Recognizing that incrementing the counter involves adding 1 to the value feature and then optionally performing a set of independent actions, we could redesign the base class to include a method whose job is to call those extra actions.

In the base class it will do nothing:

Page 18: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class COUNTER

feature

value : INTEGER

increment is do value := value + 1 signal end

signal is do -- No extra actions in this class endend

COUNTER

Now in the subclasses, we redefine signal, not increment.

Page 19: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class AUDIBLE_COUNTER

inherit COUNTER redefine signal end

feature

signal is do io.put_string("Beep!") endend

AUDIBLE_COUNTER

If we write a main program containing the code:

Page 20: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

c : COUNTER

create { AUDIBLE_COUNTER } cc.increment

MAIN

the increment routine from the base class will be called.

Page 21: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Overriding

When the increment routine from the base class will is called, it will, however, call signal, which has been redefined.

So the increment method in the COUNTER class calls the signal method from the AUDIBLE_COUNTER class, thanks to dynamic binding, and a beep is emitted.

VISIBLE_COUNTER can be defined in the same way as AUDIBLE_COUNTER, and the MULTIMEDIA_COUNTER is able to reuse the code from these two classes without any replication:

Page 22: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MULTIMEDIA_COUNTER

inherit AUDIBLE_COUNTER redefine signal end

VISIBLE_COUNTER redefine signal end

feature

signal is do Precursor {AUDIBLE_COUNTER} Precursor {VISIBLE_COUNTER} endend

MUTIMEDIA_COUNTER

Page 23: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Overriding

This is probably a better solution to the design of this hierarchy than the one which involved including all functionality in the increment method.

However, the need for this design only emerged as further classes were considered, and implementing it required changing the existing classes in the hierarchy, or refactoring. If this was not possible, perhaps because library classes were being extended, other solutions would have to be found.

Page 24: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Overriding

Eiffel textbooks often describe other complex schemes using repeated inheritance, which make extensive use of the renaming facility and other techniques in designing class hierarchies.

However, the Precursor construct is simpler to understand, and makes the other approach unnecessary in many cases, so we will not go into the details here.

Page 25: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Quiz Solutions

a. This will not compile.

b.

Beep

1

2 marks

2 marks

Page 26: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class AUDIBLE_COUNTERinherit COUNTER rename make as make_counter redefine increment endcreate makefeature beep_tone : STRING make( m : INTEGER ; b : STRING ) is do make_counter( m ) beep_tone := b end increment is

...end

2 marks

2 marks

2 marks

Page 27: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class VISIBLE_COUNTER

inherit COUNTER redefine increment end

create make

feature

increment is do Precursor io.put_string("Flash!%N") endend

2 marks

Page 28: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class MULTIMEDIA_COUNTER

inherit AUDIBLE_COUNTER redefine signal end

VISIBLE_COUNTER redefine signal end

feature

signal is do Precursor {AUDIBLE_COUNTER} Precursor {VISIBLE_COUNTER} endend

1 marks

2 marks

1 marks

1 marks

Page 29: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class COUNTER

feature

value : INTEGER

increment is do value := value + 1 signal end

signal is do -- No extra actions in this class endend

2 marks

2 marks

Page 30: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class AUDIBLE_COUNTER

inherit COUNTER redefine signal end

feature

signal is do io.put_string("Beep!") endend

2 marks

2 marks

Page 31: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Inheritance and DBC

In Eiffel, assertions and hence contracts are inherited by subclasses and can be refined in various ways, but they cannot be removed.

This means that as well as having the same interface, subclasses must exhibit the same behaviour as the parent class and hence provides a stronger form of substitutability than can be achieved in C++ or Java.

For example, suppose that the COUNTER class included the following contract:

Page 32: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class COUNTER

feature

value : INTEGER

increment is do value := value + 1 ensure value = old value + 1 endend

COUNTER

Page 33: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Inheritance and DBC

Consider the version of the MULTIMEDIA_COUNTER which tried to implement this method by calling the two versions it had inherited, leading to the value feature being incremented twice.

This programming error would be picked up by the DBC, as the new increment routine would violate the inherited postcondition.

This demonstrates a strong reason for including even apparently trivial pre- and post-conditions in contracts.

Page 34: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Redefining Inherited Contracts

When a subclass redefines a routine, it can redefine the inherited contract in certain ways.

It can also redefine the class invariant, to take account for example of new features that may have been added in the subclass.

The basic requirement is that the new contract should be consistent with the old, in the sense that calling a routine with the new contract through dynamic binding should not cause any unexpected problems.

Page 35: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Redefining Inherited Contracts

Suppose for example that we are designing the control software for an engine that makes use of a cooling fan.

The manufacturers of the fan guarantee that its energy output will always be within a certain range, but unfortunately the hardware is such that the speed can only changed gradually, and not very accurately.

The contract for the fan might be expressed using assertions in the following way:

Page 36: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class COOLING_FAN

feature

energy_output, speed : DOUBLE

increase_speed( inc : DOUBLE ) is require inc <= 5 do -- change the speed inaccurately ensure speed - old speed <= inc end

invariant

0 <= energy_output and then energy_output <= 100

end

COOLING_FAN

Page 37: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class ENGINEfeature

get_cooler( fan : COOLING_FAN ) is do fan.increase_speed(4) endend

ENGINEThe engine class will be a client of the fan class:

Now suppose that the manufacturers produce an improved and more efficient fan. It allows more accurate setting of the speed, and is more flexible to use, and it has a lower energy output:

Page 38: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class NEW_FAN

inherit COOLING_FAN redefine increase_speed end

feature increase_speed( inc : DOUBLE ) is require else inc <= 10 do -- increase speed more accurately ensure then speed = old speed + inc end

invariant energy_output <= 60end

NEW_FAN

Page 39: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Invariants

Now, suppose that a new fan is installed in the engine and that the get_cooler routine is called. Although the parameter of this method has type COOLING_FAN, at run-time it will be passed an instance of the class NEW_FAN. Could this cause a run-time error?

Page 40: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Invariants

The invariant for the new fan is stronger than the original one: if the energy output is less than 60 it must also be less than 100.

In the context of the engine class, this is fine: the new fan will always be operating within the limits assumed for the old fan. In other words, subclasses can safely strengthen invariants.

Suppose the new fan class had a higher energy output and tried to define a weaker new invariant:

Page 41: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

class NEW_FAN...invariant energy_output <= 150end

NEW_FAN

Contrary to appearances, this does not change the specification of the class. New invariants are added to the old ones using the and operator, so the complete invariant for the NEW_FAN class in this case would be:

0 <= energy_output and then energy_output <= 100 and then energy_output <= 150

This is true in exactly the same situations as the original invariant, i.e., when the energy_output is between 0 and 100.

Page 42: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Weakened Preconditions

The precondition in NEW_FAN has been weakened, in the sense of being implied by the precondition in the parent class.

If the speed increment is less than 5, it will necessarily be less than 10.

This is safe: the engine is written on the assumption that only changes of up to 5 are permitted.

The fact that the new fan is more flexible will not affect the safety of the engine program.

Page 43: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Weakened Preconditions

Preconditions in child classes are specified with a require else clause.

This has the effect of combining the old and new preconditions with the boolean or operator.

This means that trying to write a stronger precondition has no effect: if the new fan class had a precondition inc <= 2, which would cause the original engine program to throw an exception, the overall precondition would be:

Page 44: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Weakened Preconditions

inc <= 5 or else inc <= 2

which is logically identical to the original precondition.

Page 45: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Postconditions

The postcondition in NEW_FAN has been strengthened: it implies the postcondition in the parent class.

If the new speed is exactly the old speed plus the increment, the difference between them is less than or equal to the increment, as required by the original postcondition.

This is safe, as the result of the improved routine is within the bounds promised by the parent class.

Page 46: Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures.

Postconditions

Postconditions in child classes are specified with a ensure then clause.

This has the effect of combining the old and new postconditions with the boolean and operator, and as with invariants means that an attempt to write a weaker postcondition in a subclass has no effect.