Inheritance Readings: OOSCS2 Chapters 14 – 16
EECS3311 A: Software Design Fall 2019
CHEN-WEI WANG
Why Inheritance: A Motivating Example
Problem: A student management system stores data about students. There are two kinds of university students: resident students and non-resident students. Both kinds of students have a name and a list of registered courses. Both kinds of students are restricted to register for no more than 30 courses. When calculating the tuition for a student, a base amount is first determined from the list of courses they are currently registered (each course has an associated fee). For a non-resident student, there is a discount rate applied to the base amount to waive the fee for on-campus accommodation. For a resident student, there is a premium rate applied to the base amount to account for the fee for on-campus accommodation and meals. Tasks: Design classes that satisfy the above problem statement. At runtime, each type of student must be able to
register a course and calculate their tuition fee.
3 of 62
Aspects of Inheritance
● Code Reuse ● Substitutability
○ Polymorphism and Dynamic Binding[ compile-time type checks ]
○ Sub-contracting 2 of 62
[ runtime behaviour checks ]
The COURSE Class
class
COURSE
create — Declare commands that can be used as constructors make
feature — Attributes title: STRING
fee: REAL
feature — Commands
make (t: STRING; f: REAL)
— Initialize a course with title ’t’ and fee ’f’.
do
title := t
fee := f end
end
4 of 62
No Inheritance: RESIDENT STUDENT Class
class
create make
feature — Attributes
name: STRING
courses: LINKED_LIST[COURSE]
premium rate: REAL
feature — Constructor make (n: STRING)
do name := n ; create courses.make end feature — Commands
set pr (r: REAL) do premium rate := r end
register (c: COURSE) do courses.extend (c) end feature — Queries
tuition: REAL local base: REAL do base := 0.0
across courses as c loop base := base + c.item.fee end
Result := base * premium rate
end end
RESIDENT STUDENT
5 of 62
No Inheritance: Testing Student Classes
test_students: BOOLEAN local
c1, c2: COURSE
jim: RESIDENT_STUDENT jeremy: NON_RESIDENT_STUDENT
do
create c1.make (“EECS2030”, 500.0) create c2.make (“EECS3311”, 500.0) create jim.make (“J. Davis”) jim.set_pr (1.25)
jim.register (c1)
jim.register (c2)
Result := jim.tuition = 1250
check Result end
create jeremy.make (“J. Gibbons”) jeremy.set_dr (0.75) jeremy.register (c1) jeremy.register (c2)
Result := jeremy.tuition = 750
end
7 of 62
No Inheritance: NON RESIDENT STUDENT Class
class
create make
feature — Attributes
name: STRING
courses: LINKED_LIST[COURSE]
discount rate: REAL
feature — Constructor make (n: STRING)
do name := n ; create courses.make end feature — Commands
set dr (r: REAL) do discount rate := r end
register (c: COURSE) do courses.extend (c) end feature — Queries
tuition: REAL local base: REAL do base := 0.0
across courses as c loop base := base + c.item.fee end
Result := base * discount rate end
end
NON RESIDENT STUDENT
6 of 62
No Inheritance:
Issues with the Student Classes
● Implementations for the two student classes seem to work. But can you see any potential problems with it?
● The code of the two student classes share a lot in common.
● Duplicates of code make it hard to maintain your software!
● This means that when there is a change of policy on the common part, we need modify more than one places.
⇒ This violates the Single Choice Principle :
when a change is needed, there should be a single place (or a minimal number of places) where you need to make that change.
8 of 62
No Inheritance: Maintainability of Code (1)
What if a new way for course registration is to be implemented? e.g.,
register(Course c) do
if courses.count >= MAX_CAPACITY then — Error: maximum capacity reached.
else
courses.extend (c) end
end
We need to change the register commands in both student classes!
⇒ Violation of the Single Choice Principle 9 of 62
No Inheritance:
A Collection of Various Kinds of Students
How do you define a class StudentManagementSystem that contains a list of resident and non-resident students?
class STUDENT_MANAGEMENT_SYSETM
rs : LINKED_LIST[RESIDENT STUDENT]
nrs : LINKED_LIST[NON RESIDENT STUDENT]
add_rs (rs: RESIDENT STUDENT) do … end
add_nrs (nrs: NON RESIDENT STUDENT) do … end register_all (Course c) — Register a common course ’c’.
do
across rs as c loop c.item.register (c) end across nrs as c loop c.item.register (c) end
end end
But what if we later on introduce more kinds of students?
Inconvenient to handle each list of students, in pretty much the
same manner, separately! 11 of 62
No Inheritance: Maintainability of Code (2)
What if a new way for base tuition calculation is to be implemented?
e.g.,
tuition: REAL
local base: REAL
do base := 0.0
across courses as c loop base := base + c.item.fee end Result := base * inflation rate * …
end
We need to change the tuition query in both student classes.
⇒ Violation of the Single Choice Principle 10 of 62
Inheritance Architecture
STUDENT
inherit
inherit
RESIDENT STUDENT NON RESIDENT STUDENT
12 of 62
Inheritance: The STUDENT Parent Class
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
16
17
class
create make
feature — Attributes
name: STRING
courses: LINKED_LIST[COURSE]
feature — Commands that can be used as constructors.
make (n: STRING) do name := n ; create courses.make end feature — Commands
register (c: COURSE) do courses.extend (c) end feature — Queries
tuition: REAL local base: REAL do base := 0.0
across courses as c loop base := base + c.item.fee end
Result := base end
end
STUDENT
13 of 62
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
Inheritance:
The NON RESIDENT STUDENT Child Class
class
NON_RESIDENT_STUDENT
inherit
STUDENT
redefine tuition end
create make
feature — Attributes
discount rate : REAL feature — Commands
set dr (r: REAL) do discount_rate := r end feature — Queries
tuition: REAL
local base: REAL
do base := Precursor ; Result := base * discount rate end
end
● L3:NONRESIDENTSTUDENTinheritsallfeaturesfromSTUDENT.
● Thereisnoneedtorepeattheregistercommand
15of62 ● L14:PrecursorreturnsthevaluefromquerytuitioninSTUDENT.
1 2 3 4 5 6 7 8 9
10 11
12 13 14 15
Inheritance:
The RESIDENT STUDENT Child Class
class
RESIDENT_STUDENT
inherit
STUDENT
redefine tuition end
create make
feature — Attributes
premium rate : REAL feature — Commands
set pr (r: REAL) do premium_rate := r end feature — Queries
tuition: REAL
local base: REAL
do base := Precursor ; Result := base * premium rate end
end
● L3:RESIDENTSTUDENTinheritsallfeaturesfromSTUDENT.
● Thereisnoneedtorepeattheregistercommand
14of62 ● L14:PrecursorreturnsthevaluefromquerytuitioninSTUDENT.
Inheritance Architecture Revisited
inherit
inherit
RESIDENT STUDENT NON RESIDENT STUDENT ● The class that defines the common features (attributes,
STUDENT
commands, queries) is called the parent , super , or ancestor class.
● Each “specialized” class is called a child , sub , or
descendent class. 16 of 62
Using Inheritance for Code Reuse
Inheritance in Eiffel (or any OOP language) allows you to:
○ Factor out common features (attributes, commands, queries) in a
separate class.
e.g., the STUDENT class
○ Define an “specialized” version of the class which:
● inheritsdefinitionsofallattributes,commands,andqueries e.g., attributes name, courses
e.g., command register
e.g., query on base amount in tuition
This means code reuse and elimination of code duplicates!
● definesnewfeaturesifnecessary
e.g., set pr for RESIDENT STUDENT e.g., set dr for NON RESIDENT STUDENT
● redefinesfeaturesifnecessary
e.g., compounded tuition for RESIDENT STUDENT e.g., discounted tuition for NON RESIDENT STUDENT
17 of 62
Static Type vs. Dynamic Type
● In object orientation , an entity has two kinds of types:
○ static type is declared at compile time [ unchangeable ]
An entity’s ST determines what features may be called upon it. ○ dynamic type is changeable at runtime
● In Java: ● In Eiffel:
Student s = new Student(“Alan”);
Student rs = new ResidentStudent(“Mark”);
local s: STUDENT rs: STUDENT
do create {STUDENT} s.make (“Alan”)
create {RESIDENT STUDENT} rs.make (“Mark”)
○ In Eiffel, the dynamic type can be omitted if it is meant to be the same as the static type:
19 of 62
local s: STUDENT
do create s.make (“Alan”)
Testing the Two Student Sub-Classes
test_students: BOOLEAN local
c1, c2: COURSE
jim: RESIDENT_STUDENT ; jeremy: NON_RESIDENT_STUDENT do
create c1.make (“EECS2030”, 500.0); create c2.make (“EECS3311”, 500.0) create jim.make (“J. Davis”)
jim.set_pr (1.25) ; jim.register (c1); jim.register (c2)
Result := jim.tuition = 1250
check Result end
create jeremy.make (“J. Gibbons”)
jeremy.set_dr (0.75); jeremy.register (c1); jeremy.register (c2) Result := jeremy.tuition = 750
end
maintainable using inheritance . 18 of 62
● The software can be used in exactly the same way as before (because we did not modify feature signatures).
● But now the internal structure of code has been made
Inheritance Architecture Revisited
s1,s2,s3: STUDENT ; rs: RESIDENT STUDENT ; nrs : NON RESIDENT STUDENT create {STUDENT} s1.make (“S1”)
create {RESIDENT STUDENT} s2.make (“S2”)
create {NON RESIDENT STUDENT} s3.make (“S3”)
create {RESIDENT STUDENT} rs.make (“RS”) create {NON RESIDENT STUDENT} nrs.make (“NRS”)
register (Course c)
tuition: REAL
name: STRING
courses: LINKED_LIST[COUNRSE]
/* new features */
discount_rate: REAL set_dr (r: REAL)
/* redefined features */ tuition: REAL
STUDENT
/* new features */
premium_rate: REAL set_pr (r: REAL)
/* redefined features */ tuition: REAL
RESIDENT_STUDENT
NON_RESIDENT_STUDENT
name
courses
reg
tuition
pr
set pr
dr
set dr
s1.
s2.
s3.
rs.
nrs.
20 of 62
✓× ✓× ✓× ✓✓× ✓×✓
Polymorphism: Intuition (1)
1 2 3 4 5 6 7 8 9
local
s: STUDENT
rs: RESIDENT_STUDENT do
create s.make (“Stella”) create rs.make (“Rachael”) rs.set_pr (1.25)
s := rs /* Is this valid? */ rs := s /* Is this valid? */
● Which one of L8 and L9 is valid? Which one is invalid?
○ L8: What kind of address can s store? [ STUDENT ]
∴ The context object s is expected to be used as: ● s.register(eecs3311)ands.tuition
○ L9: What kind of address can rs store? [ RESIDENT STUDENT ] ∴ The context object rs is expected to be used as:
● rs.register(eecs3311)andrs.tuition
21 of 62 ● rs.set pr (1.50) [increase premium rate]
Polymorphism: Intuition (3)
1 2 3 4 5
6● s := rs (L5) should be valid:
23 of 62
local s: STUDENT ; rs: RESIDENT_STUDENT do create {STUDENT} s.make (“Stella”)
create {RESIDENT_STUDENT} rs.make (“Rachael”) rs.set_pr (1.25)
s := rs /* Is this valid? */
rs := s /* Is this valid? */
s:STUDENT
rs:RESIDENT_STUDENT
STUDENT
name “Stella”
courses
RESIDENT_STUDENT
…
…
● Since s is declared of type STUDENT, a subsequent call s.set pr(1.50) is never expected.
● s is now pointing to a RESIDENT STUDENT object. ● Then, what would happen to s.tuition?
name courses premium_rate
“Rachael”
1.25
OK ∵ s.premium rate is just never used!!
Polymorphism: Intuition (2)
1 2 3 4 5
6● rs := s (L6) should be invalid:
local s: STUDENT ; rs: RESIDENT_STUDENT do create {STUDENT} s.make (“Stella”)
create {RESIDENT_STUDENT} rs.make (“Rachael”) rs.set_pr (1.25)
s := rs /* Is this valid? */
rs := s /* Is this valid? */
s:STUDENT
rs:RESIDENT_STUDENT
STUDENT
name “Stella”
courses
RESIDENT_STUDENT
…
…
● rs declared of type RESIDENT STUDENT
∴ calling rs.set pr(1.50) can be expected.
name courses premium_rate
“Rachael”
1.25
● rs is now pointing to a STUDENT object.
● Then, what would happen to rs.set pr(1.50)?
22 of 62
CRASH ∵ rs.premium rate is undefined!!
Dynamic Binding: Intuition (1)
1 2 3 4 5 6 7 8
local c : COURSE ; s : STUDENT
do crate c.make (“EECS3311”, 100.0)
create {RESIDENT STUDENT} rs.make(“Rachael”) create {NON RESIDENT STUDENT} nrs.make(“Nancy”) rs.set_pr(1.25); rs.register(c) nrs.set_dr(0.75); nrs.register(c)
; check .tuition = 125.0 end ; check .tuition = 75.0 end
s := rs;
s := nrs;
After s := rs (L7), s points to a RESIDENT STUDENT object. ⇒ Calling .tuition applies the premium rate.
24 of 62
s
s
s
rs:RESIDENT_STUDENT
s:STUDENT
nrs:NON_RESIDENT_STUDENT
RESIDENT_STUDENT
name courses
“Rachael”
premium_rate 1.25
NON_RESIDENT_STUDENT name “Nancy”
courses
discount_rate 0.75
COURSE title “EECS3311”
fee 100.0
Dynamic Binding: Intuition (2)
1 2 3 4 5 6 7 8
local c : COURSE ; s : STUDENT
do crate c.make (“EECS3311”, 100.0)
create {RESIDENT STUDENT} rs.make(“Rachael”) create {NON RESIDENT STUDENT} nrs.make(“Nancy”) rs.set_pr(1.25); rs.register(c) nrs.set_dr(0.75); nrs.register(c)
; check .tuition = 125.0 end ; check .tuition = 75.0 end
s := rs;
s := nrs;
After s:=nrs (L8), s points to a NON RESIDENT STUDENT object. ⇒ Calling .tuition applies the discount rate.
25 of 62
s
s
s
rs:RESIDENT_STUDENT
s:STUDENT
nrs:NON_RESIDENT_STUDENT
RESIDENT_STUDENT
name courses
“Rachael”
premium_rate 1.25
NON_RESIDENT_STUDENT name “Nancy”
courses
discount_rate 0.75
COURSE title “EECS3311”
fee 100.0
ES TEST: Expecting to Fail Postcondition (1) model
tests
feature Test Commands for Contract Violations test_withdraw_postcondition_violation
local
acc: BAD_ACCOUNT_WITHDRAW
do
create acc.make (“Alan”, 100)
Violation of Postcondition
with tag “balance_deduced” expected acc.withdraw (50)
end
27 of 62
ACCOUNT
feature Commands
withdraw (amount: INTEGER)
require
non_negative_amount: amount > 0
affordable_amount: amount ≤ balance do
balance := balance amount
ensure
balance_deduced: balance = old balance amount
end
BAD_ACCOUNT_WITHDRAW
feature Redefined Commands withdraw (amount: INTEGER) ++
do
Precursor (amount)
Wrong Implementation balance := balance + 2 * amount
end
TEST_ACCOUNT
acc
DbC: Contract View of Supplier
Any potential client who is interested in learning about the kind of services provided by a supplier can look through the
(without showing any implementation details):
contract view
class ACCOUNT create
make
feature — Attributes owner : STRING
balance : INTEGER feature — Constructors
make(nn: STRING; nb: INTEGER) require — precondition
positive balance: nb > 0 end
feature — Commands withdraw(amount: INTEGER)
require — precondition
non negative amount: amount > 0
affordable amount: amount <= balance -- problematic, why?
ensure -- postcondition
balance deducted: balance = old balance - amount
end
invariant -- class invariant
positive balance: balance > 0 end
26 of 62
ES TEST: Expecting to Fail Postcondition (2.1)
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
class
BAD_ACCOUNT_WITHDRAW
inherit
ACCOUNT
redefine withdraw end
create
make
feature — redefined commands withdraw(amount: INTEGER)
do
Precursor(amount)
— Wrong implementation
balance := balance + 2 * amount
end end
○ L3–5: BAD ACCOUNT WITHDRAW.withdraw inherits postcondition from ACCOUNT.withdraw: balance = old balance – amount.
○ L11 calls correct implementation from parent class ACCOUNT.
○ L13 makes overall implementation incorrect. 28 of 62
ES TEST: Expecting to Fail Postcondition (2.2)
1
2 inherit ES TEST
3 create make
4 feature — Constructor for adding tests 5 make
6 do
class TEST_ACCOUNT
7
8
9 end
add violation case with tag (“balance_deducted”, agent test_withdraw_postcondition_violation)
10 feature — Test commands (test to fail) 11 test_withdraw_postcondition_violation 12 local
13
14 do
acc: BAD_ACCOUNT_WITHDRAW
15
16
17
18
19 end 20 end
comment (“test: expected postcondition violation of withdraw”) create acc.make (“Alan”, 100)
— Postcondition Violation with tag “balance_deduced” to occur. acc.withdraw (50)
29 of 62
Multi-Level Inheritance Architecture (1)
31 of 62
DOMESTIC_STUDENT
STUDENT
FOREIGN_STUDENT
DOMESTIC_RESIDENT_STUDENT
DOMESTIC_NON_RESIDENT_STUDENT
FOREIGN_RESIDENT_STUDENT
FOREIGN_NON_RESIDENT_STUDENT
●
Exercise
Recall from the “Writing Complete Postconditions” lecture:
class BANK
deposit_on_v5 (n: STRING; a: INTEGER)
do … — Put Correct Implementation Here. ensure
…
end end
others unchanged :
across old accounts.deep twin as cursor all cursor.item.owner /∼ n implies
cursor.item ∼ account_of (cursor.item.owner) end
How do you create a “bad” descendant of BANK that violates this postcondition?
class BAD_BANK_DEPOSIT
inherit BANK redefine deposit end feature — redefined feature
deposit_on_v5 (n: STRING; a: INTEGER) do Precursor (n, a)
accounts[accounts.lower].deposit(a)
end end
30 of 62
Multi-Level Inheritance Architecture (2)
SMART_PHONE
dial — basic feature surf_web — basic feature
IOS
surf_web — redefined using safari facetime — new feature
quick_take
zoomage
surf_web — redefined using firefox skype — new feature
side_sync
ANDROID
IPHONE_XS_MAX
IPHONE_11_PRO
HUAWEI
SAMSUNG
32 of 62
HUAWEI_P30_PRO
HUAWEI_MATE_20_PRO
GALAXY_S10
GALAXY_S10_PLUS
Inheritance Forms a Type Hierarchy
● A (data) type denotes a set of related runtime values.
○ Every class can be used as a type: the set of runtime objects.
● Use of inheritance creates a hierarchy of classes: ○ (Implicit) Root of the hierarchy is ANY.
○ Each inherit declaration corresponds to an upward arrow.
○ The inherit relationship is transitive: when A inherits B and B
inherits C, we say A indirectly inherits C.
e.g., Every class implicitly inherits the ANY class. ● Ancestor vs. Descendant classes:
○ The ancestor classes of a class A are: A itself and all classes that A directly, or indirectly, inherits.
● Ainheritsallfeaturesfromitsancestorclasses.
∴ A’s instances have a wider range of expected usages (i.e., attributes, queries, commands) than instances of its ancestor classes.
○ The descendant classes of a class A are: A itself and all classes that directly, or indirectly, inherits A.
33of62 ● CodedefinedinAisinheritedtoallitsdescendantclasses.
Substitutions via Assignments
● By declaring , reference variable v1 will store the address of an object of class C1 at runtime.
v1:C1
● By declaring , reference variable v2 will store the address of an object of class C2 at runtime.
v2:C2
● Assignment copies the address stored in v2 into v1. ○ v1 will instead point to wherever v2 is pointing to. ]
v1:=v2
[ object alias
● In such assignment , we say that we an object of type C1 with an object of type C2.
● Substitutions are subject to rules! 35 of 62
…
…
C1
v1
v2
C2
…
…
substitute
v1:=v2
Inheritance Accumulates Code for Reuse
● The lower a class is in the type hierarchy, the more code it accumulates from its ancestor classes:
○ A descendant class inherits all code from its ancestor classes. ○ A descendant class may also:
● Declarenewattributes.
● Definenewqueriesorcommands.
● Redefine inherited queries or commands.
● Consequently:
○ When being used as context objects ,
instances of a class’ descendant classes have a wider range of
expected usages (i.e., attributes, commands, queries).
○ When expecting an object of a particular class, we may substitute
it with an object of any of its descendant classes.
○ e.g., When expecting a STUDENT object, substitute it with either a
RESIDENT STUDENT or a NON RESIDENT STUDENT object.
○ Justification: A descendant class contains at least as many
34 of 62features as defined in its ancestor classes (but not vice versa!).
Rules of Substitution
Given an inheritance hierarchy:
1. When expecting an object of class A, it is safe to substitute it
with an object of any descendant class of A (including A). ○ e.g., When expecting an IOS phone, you can substitute it with
either an IPHONE XS MAX or IPHONE 11 PRO.
○ ∵ Each descendant class of A is guaranteed to contain all code
of (non-private) attributes, commands, and queries defined in A. ○ ∴ All features defined in A are guaranteed to be available in the
new substitute.
2. When expecting an object of class A, it is unsafe to substitute
it with an object of any .
○ e.g., When expecting an IOS phone, you cannot substitute it with
just a SMART PHONE, because the facetime feature is not
supported in an ANDROID phone.
○ ∵ Class A may have defined new features that do not exist in any
of its parent’s ancestor classes . 36 of 62
ancestor class of A’s parent
Reference Variable: Static Type
● A reference variable’s static type is what we declare it to be. ○ e.g., declares jim’s static type as STUDENT.
jim:STUDENT
○ e.g.,
declares a variable my phone of static type SmartPhone.
my phone:SMART PHONE
○ The static type of a reference variable never changes.
● For a reference variable v, its static type defines the
.
● A feature call v.m(. . . ) is compilable if m is defined in .
○ e.g., After declaring , we
● maycallregisterandtuitiononjim
● maynotcallsetpr(specifictoaresidentstudent)orsetdr
C
expected usages of v as a context object
C
jim:STUDENT
(specific to a non-resident student) on jim
○ e.g., After declaring , we
my phone:SMART PHONE
● maycalldialandsurfwebonmyphone
● maynotcallfacetime(specifictoanIOSphone)orskype(specific
37 of 62
to an Android phone) on my phone
Reference Variable: Changing Dynamic Type (1)
Re-assigning a reference variable to a newly-created object:
○ Substitution Principle : the new object’s class must be a descendant class of the reference variable’s static type.
○ e.g., Given the declaration :
● changesthedynamictypeofjimtoRESIDENTSTUDENT.
jim:STUDENT
create {RESIDENT STUDENT} jim.make(“Jim”)
● changesthedynamictypeofjimtoNONRESIDENTSTUDENT.
○ e.g., Given an alternative declaration :
● e.g., isillegal
create {NON RESIDENT STUDENT} jim.make(“Jim”)
because STUDENT is not a descendant class of the static type of jim (i.e., RESIDENT STUDENT).
jim:RESIDENT STUDENT
create {STUDENT} jim.make(“Jim”)
39 of 62
Reference Variable: Dynamic Type
A reference variable’s dynamic type is the type of object that it is currently pointing to at runtime.
○ The dynamic type of a reference variable may change whenever we re-assign that variable to a different object.
○ There are two ways to re-assigning a reference variable. 38 of 62
Reference Variable: Changing Dynamic Type (2)
Re-assigning a reference variable v to an existing object that is referenced by another variable other (i.e., v := other):
40 of 62
○ Substitution Principle : the static type of other must be a descendant class of v’s static type.
○ e.g.,
jim: STUDENT ; rs: RESIDENT STUDENT; nrs: NON RESIDENT STUDENT create {STUDENT} jim.make (…)
create {RESIDENT STUDENT} rs.make (…)
create {NON RESIDENT STUDENT} nrs.make (…)
● rs := jim × ● nrs := jim × ● jim := rs ✓
changes the dynamic type of jim to the dynamic type of rs
● jim := nrs ✓
changes the dynamic type of jim to the dynamic type of nrs
Polymorphism and Dynamic Binding (1)
●
●
Polymorphism : An object variable may have “multiple
possible shapes” (i.e., allowable dynamic types).
○ Consequently, there are multiple possible versions of each feature
that may be called.
● e.g.,3possibilitiesoftuitiononaSTUDENTreferencevariable:
In STUDENT: base amount
In RESIDENT STUDENT: base amount with premium rate
In NON RESIDENT STUDENT: base amount with discount rate
Dynamic binding : When a feature m is called on an object variable, the version of m corresponding to its “current shape” (i.e., one defined in the dynamic type of m) will be called.
jim: STUDENT; rs: RESIDENT STUDENT; nrs: NON STUDENT create {RESIDENT STUDENT} rs.make (…)
create {NON RESIDENT STUDENT} nrs.nrs (…)
jim := rs
jim.tuitoion; /* version in RESIDENT STUDENT */ jim := nrs
jim.tuition; /* version in NON RESIDENT STUDENT */
41 of 62
Polymorphism and Dynamic Binding (2.2)
test_dynamic_binding_students: BOOLEAN local
jim: STUDENT
rs: RESIDENT_STUDENT
nrs: NON_RESIDENT_STUDENT c: COURSE
do
create c.make (“EECS3311”, 500.0)
create {STUDENT} jim.make (“J. Davis”)
create {RESIDENT STUDENT} rs.make (“J. Davis”) rs.register (c)
rs.set_pr (1.5)
jim := rs
Result := jim.tuition = 750.0
check Result end
create {NON RESIDENT STUDENT} nrs.make (“J. Davis”) nrs.register (c)
nrs.set_dr (0.5)
jim := nrs
Result := jim.tuition = 250.0
end
43 of 62
Polymorphism and Dynamic Binding (2.1)
1 2 3 4 5 6 7 8 9
10 11 12 13 14
L8:
L9: 42 of 62
test_polymorphism_students
local
jim: STUDENT
rs: RESIDENT STUDENT
nrs: NON RESIDENT STUDENT
do
create {STUDENT} jim.make (“J. Davis”)
create {RESIDENT STUDENT} rs.make (“J. Davis”) create {NON RESIDENT STUDENT} nrs.make (“J. Davis”) jim := rs ✓
rs := jim ×
jim := nrs ✓
rs := jim ×
end
In (L3, L7), (L4, L8), (L5, L9), ST = DT , so we may abbreviate: L7:
create jim.make (“J. Davis”)
create rs.make (“J. Davis”)
create nrs.make (“J. Davis”)
Reference Type Casting: Motivation
1 2 3 4
local jim: STUDENT; rs: RESIDENT STUDENT
do create {RESIDENT STUDENT} jim.make (“J. Davis”)
rs := jim rs.setPremiumRate(1.5)
● Line 2 is legal: RESIDENT_STUDENT is a descendant class of the static type of jim (i.e., STUDENT).
● Line 3 is illegal: jim’s static type (i.e., STUDENT) is not a descendant class of rs’s static type (i.e., RESIDENT_STUDENT).
in
● Eiffel compiler is unable to infer that jim’s
Line 4 is RESIDENT_STUDENT. [ ] ● Force the Eiffel compiler to believe so, by replacing L3, L4 by a
44 of 62
(which changes the ST of jim):
dynamic type
Undecidable
type cast
temporarily
check attached {RESIDENT STUDENT} jim as rs_jim then rs := rs_jim
rs.set_pr (1.5)
end
Reference Type Casting: Syntax
1 2 3 4
check attached {RESIDENT STUDENT} jim as rs_jim then rs := rs_jim
rs.set_pr (1.5)
end
L1 is an assertion:
○ ○
is a Boolean expression
that is to be evaluated at .
● Ifitevaluatestotrue,thenthe expressionhastheeffect
of assigning “the cast version” of jim to a new variable rs jim. ● Ifitevaluatestofalse,thenaruntimeassertionviolationoccurs.
Dynamic Binding : Line 4 executes the correct version of set pr. ● It is approximately the same as following Java code:
45 of 62
attached RESIDENT STUDENT jim
runtime
as rs jim
if(jim instanceof ResidentStudent) { ResidentStudent rs = (ResidentStudent) jim; rs.set_pr(1.5);
}
else { throw new Exception(“Cast Not Done.”); }
Notes on Type Cast (2)
● Given v of static type ST , it is violation-free to cast v to C , as long as C is a descendant or ancestor class of ST .
● Why Cast?
○ Without cast, we can only call features defined in ST on v.
○ By casting v to C , we create an alias of the object pointed by v,
with the new static type C .
⇒ All features that are defined in C can be called.
my_phone: IOS
create {IPHONE 11 PRO} my_phone.make
check attached {IPHONE 11 PRO} my_phone as ip11_pro then
— can now call features defined in IPHONE_11_PRO on ip11_pro
— dial, surf_web, facetime, quick_take ✓ skype, side_sync, zoomage ×
end
47 of 62
— can only call features defined in IOS on myPhone
— dial, surf_web, facetime ✓ quick_take, skype, side_sync, zoomage × check attached {SMART PHONE} my_phone as sp then
— can now call features defined in SMART_PHONE on sp
— dial, surf_web ✓ facetime, quick_take, skype, side_sync, zoomage × end
Notes on Type Cast (1)
●
● What if C is not an ancestor of y’s DT?
⇒ A runtime assertion violation occurs!
∵ y’s DT cannot fulfill the expectation of C.
check attached {C} y then . . . end
always compiles
46 of 62
Notes on Type Cast (3)
A cast triggers an assertion violation if C is not along the ancestor path of v’s DT .
test_smart_phone_type_cast_violation local mine: ANDROID
do create {HUAWEI} mine.make
— ST of mine is ANDROID; DT of mine is HUAWEI
end
— ∵ SAMSUNG is not ancestor of mine’s DT (HUAWEI)
check attached {HUAWEI P30 PRO} mine as p30_pro then … end — Assertion violation
— ∵ HUAWEI_P30_PRO is not ancestor of mine’s DT (HUAWEI)
48 of 62
check attached {C} v as …
check attached {SMART PHONE} mine as sp then … end — ST of sp is SMART_PHONE; DT of sp is HUAWEI
check attached {HUAWEI} mine as huawei then … end — ST of huawei is HUAWEI; DT of huawei is HUAWEI check attached {SAMSUNG} mine as samsung then … end — Assertion violation
Polymorphism: Feature Call Arguments (1)
1 2 3 4 5
assignment (i.e., replace parameter rs by a copy of argument o):
class STUDENT_MANAGEMENT_SYSTEM {
ss : ARRAY[STUDENT] — ss[i] has static type Student add_s (s: STUDENT) do ss[0] := s end
add_rs (rs: RESIDENT STUDENT) do ss[0] := rs end add_nrs (nrs: NON RESIDENT STUDENT) do ss[0] := nrs end
● L4: is valid. ∵ RHS’s ST RESIDENT STUDENT is a descendant class of LHS’s ST STUDENT.
ss[0]:=rs
● Say we have a STUDENT MANAGEMENT SYSETM object sms: ○ ∵ call by value , attempts the following
sms.add rs(o)
rs := o
○ Whether this argument passing is valid depends on o’s static type. Rule: In the signature of a feature m, if the type of a parameter is class C, then we may call feature m by passing objects whose
static types are C’s descendants. 49 of 62
Why Inheritance:
A Polymorphic Collection of Students
How do you define a class STUDENT MANAGEMENT SYSETM that contains a list of resident and non-resident students?
class STUDENT_MANAGEMENT_SYSETM students: LINKED_LIST[STUDENT] add_student(s: STUDENT)
do
students.extend (s) end
registerAll (c: COURSE) do
across
students as s
loop
s.item.register (c) end
end end
51 of 62
Polymorphism: Feature Call Arguments (2)
test_polymorphism_feature_arguments
local
s1, s2, s3: STUDENT
rs: RESIDENT STUDENT ; nrs: NON RESIDENT STUDENT sms: STUDENT_MANAGEMENT_SYSTEM
do
create sms.make
create {STUDENT} s1.make (“s1”)
create {RESIDENT_STUDENT} s2.make (“s2”)
create {NON_RESIDENT_STUDENT} s3.make (“s3”)
create {RESIDENT_STUDENT} rs.make (“rs”)
create {NON_RESIDENT_STUDENT} nrs.make (“nrs”)
sms.add_s (s1) ✓ sms.add_s (s2) ✓ sms.add_s (s3) ✓ sms.add_s (rs) ✓ sms.add_s (nrs) ✓
sms.add_rs (s1) × sms.add_rs (s2) × sms.add_rs (s3) × sms.add_rs (rs) ✓ sms.add_rs (nrs) ×
sms.add_nrs (s1) × sms.add_nrs (s2) × sms.add_nrs (s3) × sms.add_nrs (rs) × sms.add_nrs (nrs) ✓
end
50 of 62
Polymorphism and Dynamic Binding: A Polymorphic Collection of Students
test_sms_polymorphism: BOOLEAN local
rs: RESIDENT_STUDENT
nrs: NON_RESIDENT_STUDENT
c: COURSE
sms: STUDENT_MANAGEMENT_SYSTEM
do
create rs.make (“Jim”)
rs.set_pr (1.5)
create nrs.make (“Jeremy”)
nrs.set_dr (0.5)
create sms.make
sms.add_s (rs)
sms.add_s (nrs)
create c.make (“EECS3311”, 500)
sms.register_all (c)
Result := sms.ss[1].tuition = 750 and sms.ss[2].tuition = 250
end
52 of 62
Polymorphism: Return Values (1)
1 2 3 4 5 6 7 8 9
10 11 12
class STUDENT_MANAGEMENT_SYSTEM { ss: LINKED_LIST[STUDENT]
add_s (s: STUDENT)
do
ss.extend (s) end
get_student(i: INTEGER): STUDENT require 1 <= i and i <= ss.count do
Result := ss[i] end
end
● L2: ST of each stored item (ss[i]) in the list:
● L3: ST of input parameter s:
● L7: ST of return value (Result) of get student:
● L11: ss[i]’s ST is descendant of Result’ ST .
[STUDENT] [STUDENT] [STUDENT]
Question: What can be the dynamic type of s after Line 11?
Answer: All descendant classes of Student. 53 of 62
Design Principle: Polymorphism
● When declaring an attribute
s: STUDENT
T
a: T
⇒ Choose static type which “accumulates” all features that you predict you will want to call on a.
e.g., Choose if you do not intend to be specific about which kind of student s might be.
⇒ Let dynamic binding determine at runtime which version of tuition will be called.
● What if after declaring you find yourself often needing to cast s to RESIDENT STUDENT in order to access premium rate?
s: STUDENT
check attached {RESIDENT_STUDENT} s as rs then rs.set_pr(...) end
⇒ Your design decision should have been: ● Same design principle applies to:
○ Type of feature parameters:
○ Type of queries: 55 of 62
s:RESIDENT_STUDENT
f(a: T)
q(...): T
Polymorphism: Return Values (2)
1 2 3 4 5 6 7 8 9
10 11 12 13
test_sms_polymorphism: BOOLEAN local
rs: RESIDENT_STUDENT ; nrs: NON_RESIDENT_STUDENT
c: COURSE ; sms: STUDENT_MANAGEMENT_SYSTEM do
create rs.make ("Jim") ; rs.set_pr (1.5)
create nrs.make ("Jeremy") ; nrs.set_dr (0.5)
create sms.make ; sms.add_s (rs) ; sms.add_s (nrs) create c.make ("EECS3311", 500) ; sms.register_all (c) Result :=
get_student(1).tuition = 750 and get_student(2).tuition = 250
end
● L11: get student(1)’s dynamic type? ● L11: Version of tuition?
● L12: get student(2)’s dynamic type?
[RESIDENT_STUDENT]
[RESIDENT_STUDENT] [NON_RESIDENT_STUDENT] [NON_RESIDENT_STUDENT]
● L12: Version of tuition? 54 of 62
Static Type vs. Dynamic Type: When to consider which?
● Whether or not an OOP code compiles depends only on the static types of relevant variables.
●
∵ Inferring the dynamic type statically is an undecidable problem that is inherently impossible to solve.
The behaviour of Eiffel code being executed at runtime
e.g., which version of method is called
e.g., if a check attached {...} as ... then ... end assertion error will occur
depends on the dynamic types of relevant variables.
⇒ Best practice is to visualize how objects are created (by drawing boxes) and variables are re-assigned (by drawing arrows).
56 of 62
Summary: Type Checking Rules
CODE
CONDITION TO BE TYPE CORRECT
x := y
y’s ST a descendant of x’s ST
x.f(y)
Feature f defined in x’s ST
y’s ST a descendant of f’s parameter’s ST
z := x.f(y)
Feature f defined in x’s ST
y’s ST a descendant of f’s parameter’s ST ST of m’s return value a descendant of z’s ST
check attached {C} y
check attached {C} y as temp
then x := temp end
Always compiles
C a descendant of x’s ST
check attached {C} y as temp then x.f(temp) end
Feature f defined in x’s ST
C a descendant of f’s parameter’s ST
Even if always compiles, a runtime assertion error occurs if C is not an ancestor of y’s DT!
57 of 62
check attached {C} y then . . . end
Index (2) Inheritance:
The RESIDENT STUDENT Child Class Inheritance:
The NON RESIDENT STUDENT Child Class Inheritance Architecture Revisited
Using Inheritance for Code Reuse Testing the Two Student Sub-Classes Static Type vs. Dynamic Type Inheritance Architecture Revisited Polymorphism: Intuition (1) Polymorphism: Intuition (2) Polymorphism: Intuition (3)
Dynamic Binding: Intuition (1)
Dynamic Binding: Intuition (2)
DbC: Contract View of Supplier
59 of 62
Index (1)
Aspects of Inheritance
Why Inheritance: A Motivating Example
The COURSE Class
No Inheritance: RESIDENT STUDENT Class
No Inheritance: NON RESIDENT STUDENT Class
No Inheritance: Testing Student Classes No Inheritance:
Issues with the Student Classes
No Inheritance: Maintainability of Code (1)
No Inheritance: Maintainability of Code (2) No Inheritance:
A Collection of Various Kinds of Students
Inheritance Architecture
Inheritance: The STUDENT Parent Class 58 of 62
Index (3)
ES TEST: Expecting to Fail Postcondition (1) ES TEST: Expecting to Fail Postcondition (2.1) ES TEST: Expecting to Fail Postcondition (2.2) Exercise
Multi-Level Inheritance Architecture (1) Multi-Level Inheritance Architecture (2) Inheritance Forms a Type Hierarchy Inheritance Accumulates Code for Reuse Substitutions via Assignments
Rules of Substitution
Reference Variable: Static Type
Reference Variable: Dynamic Type
Reference Variable:
Changing Dynamic Type (1)
60 of 62
Index (4)
Reference Variable:
Changing Dynamic Type (2)
Polymorphism and Dynamic Binding (1) Polymorphism and Dynamic Binding (2.1) Polymorphism and Dynamic Binding (2.2) Reference Type Casting: Motivation Reference Type Casting: Syntax
Notes on Type Cast (1)
Notes on Type Cast (2)
Notes on Type Cast (3)
Polymorphism: Feature Call Arguments (1) Polymorphism: Feature Call Arguments (2) Why Inheritance:
A Polymorphic Collection of Students
61 of 62
Index (5)
Polymorphism and Dynamic Binding: A Polymorphic Collection of Students
Polymorphism: Return Values (1)
Polymorphism: Return Values (2)
Design Principle: Polymorphism
Static Type vs. Dynamic Type: When to consider which?
Summary: Type Checking Rules
62 of 62