Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


Presentation on theme: "Inheritance 2 Ranga Rodrigo Based on Mark Priestley's Lectures."— Presentation transcript:

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

2 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:

3 class MOTHER feature make is... end class FATHER feature make is... end class CHILD inherit MOTHER rename make as make_mother end FATHER rename make as make_father end end Two parents Multiple inheritance and renaming

4 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:

5 class VISIBLE_COUNTER inherit COUNTER redefine increment end create make feature increment is do Precursor io.put_string("Flash!%N") end VISIBLE_COUNTER Inherits from COUNTER

6 class AUDIBLE_COUNTER inherit COUNTER redefine increment end create make feature increment is do value := value + 1 io.put_string("Beep%N") end AUDIBLE_COUNTER Inherits from COUNTER

7 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 Inherits from AUDIBLE_COUNTE R VISIBLE_COUNTE R

8 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.

9 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.

10 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:

11 class MULTIMEDIA_COUNTER inherit AUDIBLE_COUNTER redefine increment end VISIBLE_COUNTER redefine increment end feature increment is do... end MULTIMEDIA_COUNTER Inherits from COUNTER

12 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:

13 class MULTIMEDIA_COUNTER... feature increment is do Precursor { AUDIBLE_COUNTER } Precursor { VISIBLE_COUNTER } end MULTIMEDIA_COUNTER

14 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:

15 class MULTIMEDIA_COUNTER inherit AUDIBLE_COUNTER redefine increment end VISIBLE_COUNTER redefine increment end COUNTER redefine increment end feature increment is do Precursor { COUNTER } io.put_string("Beep!") io.put_string("Flash!") end MULTIMEDIA_COUNTER

16 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.

17 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:

18 class COUNTER feature value : INTEGER increment is do value := value + 1 signal end signal is do -- No extra actions in this class end COUNTER Now in the subclasses, we redefine signal, not increment.

19 class AUDIBLE_COUNTER inherit COUNTER redefine signal end feature signal is do io.put_string("Beep!") end AUDIBLE_COUNTER If we write a main program containing the code:

20 c : COUNTER create { AUDIBLE_COUNTER } c c.increment MAIN the increment routine from the base class will be called.

21 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:

22 class MULTIMEDIA_COUNTER inherit AUDIBLE_COUNTER redefine signal end VISIBLE_COUNTER redefine signal end feature signal is do Precursor {AUDIBLE_COUNTER} Precursor {VISIBLE_COUNTER} end MUTIMEDIA_COUNTER

23 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.

24 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.

25 Quiz Solutions a. This will not compile. b. Beep 1 2 marks

26 class AUDIBLE_COUNTER inherit COUNTER rename make as make_counter redefine increment end create make feature beep_tone : STRING make( m : INTEGER ; b : STRING ) is do make_counter( m ) beep_tone := b end increment is... end 2 marks

27 class VISIBLE_COUNTER inherit COUNTER redefine increment end create make feature increment is do Precursor io.put_string("Flash!%N") end 2 marks

28 class MULTIMEDIA_COUNTER inherit AUDIBLE_COUNTER redefine signal end VISIBLE_COUNTER redefine signal end feature signal is do Precursor {AUDIBLE_COUNTER} Precursor {VISIBLE_COUNTER} end 1 marks 2 marks 1 marks

29 class COUNTER feature value : INTEGER increment is do value := value + 1 signal end signal is do -- No extra actions in this class end 2 marks

30 class AUDIBLE_COUNTER inherit COUNTER redefine signal end feature signal is do io.put_string("Beep!") end 2 marks

31 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:

32 class COUNTER feature value : INTEGER increment is do value := value + 1 ensure value = old value + 1 end COUNTER

33 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.

34 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.

35 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:

36 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

37 class ENGINE feature get_cooler( fan : COOLING_FAN ) is do fan.increase_speed(4) end ENGINE The 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:

38 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 <= 60 end NEW_FAN

39 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?

40 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:

41 class NEW_FAN... invariant energy_output <= 150 end 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.

42 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.

43 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:

44 Weakened Preconditions inc <= 5 or else inc <= 2 which is logically identical to the original precondition.

45 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.

46 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.


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

Similar presentations


Ads by Google