Inheritance Readings: OOSCS2 Chapters 14 – 16
EECS3311 A: Software Design Fall 2018
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 58
Aspects of Inheritance
● Code Reuse ● Substitutability
○ Polymorphism and Dynamic Binding[ compile-time type checks ]
○ Sub-contracting 2 of 58
[ 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 58
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 58
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 58
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 58
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 58
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 58
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 58
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 58
Inheritance Architecture
STUDENT
inherit
inherit
RESIDENT STUDENT NON RESIDENT STUDENT
12 of 58
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 58
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
15of58 ● 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
14of58 ● 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 58
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 58
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 58
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 58
● 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 58
✓× ✓× ✓× ✓✓× ✓×✓
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 58 ● 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 58
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 58
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 58
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 58
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
Multi-Level Inheritance Architecture (2)
SMART_PHONE
dial /* basic feature */ surf_web /* basic feature */
IOS
surf_web /* redefined using safari */ facetime /* new feature */
surf_web /* redefined using firefox */ skype /* new feature */
ANDROID
IPHONE_6S
IPHONE_6S_PLUS
SAMSUNG
HTC
27 of 58
GALAXY_S6_EDGE
GALAXY_S6_EDGE_PLUS
HTC_ONE_A9
HTC_ONE_M9
Multi-Level Inheritance Architecture (1)
26 of 58
DOMESTIC_STUDENT
STUDENT
FOREIGN_STUDENT
DOMESTIC_RESIDENT_STUDENT
DOMESTIC_NON_RESIDENT_STUDENT
FOREIGN_RESIDENT_STUDENT
FOREIGN_NON_RESIDENT_STUDENT
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.
28of58 ● CodedefinedinAisinheritedtoallitsdescendantclasses.
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
29 of 58features 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 IPhone6s or IPhone6sPlus.
○ ∵ 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 SmartPhone, 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 . 31 of 58
ancestor class of A’s parent
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! 30 of 58
…
…
C1
v1
v2
C2
…
…
substitute
v1:=v2
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 defines the
.
● A feature call v.m(. . . ) is compilable if m is defined in .
○ e.g., After declaring , we
● maycallregisterandtuitiononjim
● maynotcallsetpr(specifictoaresidentstudent)orsetdr
static type
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
32 of 58
to an Android phone) on my phone
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. 33 of 58
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):
35 of 58
○ 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
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
because STUDENT is not a descendant class of the static type of jim (i.e., RESIDENT STUDENT).
create {NON RESIDENT STUDENT} jim.make(“Jim”)
jim:RESIDENT STUDENT
create {STUDENT} jim.make(“Jim”)
34 of 58
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 */
36 of 58
Polymorphism and Dynamic Binding (2.1)
1 2 3 4 5 6 7 8 9
10 11 12 13 14
L8:
L9: 37 of 58
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
39 of 58
(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
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
38 of 58
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 equivalent to the following Java code:
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.”); }
40 of 58
Notes on Type Cast (1)
● Given v of static type ST, it is compilable 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.
○ Bycastingv to C ,wechangethestatictypeofv fromST to C .
⇒ All features that are defined in C can be called.
my_phone: IOS
create {IPHONE 6S PLUS} my_phone.make
— can only call features defined in IOS on myPhone
— dial, surf_web, facetime ✓ three_d_touch, skype × check attached {SMART PHONE} my_phone as sp then
check attached {IPHONE 6S PLUS} my_phone as ip6s_plus then
— can now call features defined in IPHONE_6S_PLUS on ip6s_plus — dial, surf_web, facetime, three_d_touch ✓ skype ×
— can now call features defined in SMART_PHONE on sp
— dial, surf_web ✓ facetime, three_d_touch, skype × end
end
41 of 58
Compilable Cast vs. Exception-Free Cast (1)
1 2 3 4 5
class A end
class B inherit A end class C inherit B end class D inherit A end
local b: B ; d: D do
create {C} b.make
check attached {D} b as temp then d := temp end end
● AfterL3: b’sST isBandb’sDT isC. ● Does L4 compile?
43 of 58
[ NO ] ∵ cast type D is neither an ancestor nor a descendant of b’s ST B
Notes on Type Cast (2)
● A cast being compilable is not necessarily runtime-error-free! ● A cast triggers an assertion
check attached {C} v as …
violation if C is not along the ancestor path of v’s DT .
test_smart_phone_type_cast_violation local mine: ANDROID
do create {SAMSUNG} mine.make
— ST of mine is ANDROID; DT of mine is SAMSUNG
check attached {SMART PHONE} mine as sp then … end — ST of sp is SMART_PHONE; DT of sp is SAMSUNG
check attached {SAMSUNG} mine as samsung then … end — ST of samsung is SAMSNG; DT of samsung is SAMSUNG check attached {HTC} mine as htc then … end
— Compiles ∵ HTC is descendant of mine’s ST (ANDROID)
— Assertion violation
— ∵ HTC is not ancestor of mine’s DT (SAMSUNG)
check attached {GALAXY S6 EDGE} mine as galaxy then … end
— Compiles ∵ GALAXY_S6_EDGE is descendant of mine’s ST (ANDROID) — Assertion violation
— ∵ GALAXY_S6_EDGE is not ancestor of mine’s DT (SAMSUNG) end
42 of 58
Compilable Cast vs. Exception-Free Cast (2)
1 2 3 4 5
● Would the following fix L4?
class A end
class B inherit A end class C inherit B end class D inherit A end
local b: B ; d: D do
create {C} b.make
check attached {D} b as temp then d := temp end end
check attached {A} b as temp1 then
check attached {D} temp1 as temp2 then d := temp2 end
end
YES ∵ cast type D is an ancestor of b’s cast, temporary ST A ● What happens when executing this fix?
Assertion Violation ∵ cast type D not an ancestor of temp1’s DT C 44 of 58
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. 45 of 58
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
47 of 58
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
46 of 58
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
48 of 58
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. 49 of 58
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: 51 of 58
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? 50 of 58
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 Java 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).
52 of 58
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 then ... end
C an ancestor or a descendant of y’s ST
check attached {C} y as temp then x := temp end
C an ancestor or a descendant of y’s ST C a descendant of x’s ST
check attached {C} y as temp then x.f(temp) end
C an ancestor or a descendant of y’s ST Feature f defined in x’s ST
C a descendant of f’s parameter’s ST
Even if compiles, a runtime assertion error occurs if C is not an ancestor of y’s DT !
53 of 58
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)
Multi-Level Inheritance Architecture (1)
55 of 58
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 54 of 58
Index (3)
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) Reference Variable:
Changing Dynamic Type (2) Polymorphism and Dynamic Binding (1) Polymorphism and Dynamic Binding (2.1)
Polymorphism and Dynamic Binding (2.2)
56 of 58
Index (4)
Reference Type Casting: Motivation
Reference Type Casting: Syntax
Notes on Type Cast (1)
Notes on Type Cast (2)
Compilable Cast vs. Exception-Free Cast (1) Compilable Cast vs. Exception-Free Cast (2) Polymorphism: Feature Call Arguments (1)
Polymorphism: Feature Call Arguments (2) Why Inheritance:
A Polymorphic Collection of Students Polymorphism and Dynamic Binding:
A Polymorphic Collection of Students
Polymorphism: Return Values (1)
Polymorphism: Return Values (2)
57 of 58
Index (5)
Design Principle: Polymorphism
Static Type vs. Dynamic Type: When to consider which?
Summary: Type Checking Rules
58 of 58