Lecture 23
Subclassing, Overriding, Dynamic Dispatch
Programming Languages
UW CSE 341 - Spring 2021
Subclassing
(define color-point%
(class point% …))
No accidental overrides
This avoids accidental method-name collisions
But there is no perfect solution to all the “subclass can break if superclass changes” issues in OOP
super, final, pubment, augment, and more
Like in Java:
Unlike anything in Java:
See The Racket Guide and section materials
Simple Example: point% and color-point%
(define point%
(class object%
(super-new)
(init x y)
(define x-coord x)
(define y-coord y)
(define/public (get-x) x-coord)
(define/public (get-y) y-coord)
(define/public (dist-from-origin) …)
(define/public (dist-from-origin2) …)
(define/public (->string) …)))
Simple Example: point% and color-point%
(define point%
(class object%
(super-new)
(init x y)
(define x-coord x)
(define y-coord y)
(define/public (get-x) x-coord)
(define/public (get-y) y-coord)
(define/public (dist-from-origin) …)
(define/public (dist-from-origin2) …)
(define/public (->string) …)))
(define color-point%
(class point%
(super-new)
(init [color "black"])
(define current-color color)
(define/public (get-color) …)
(define/public (set-color! c) …)
(define/override (->string)
(string-append (get-color) ":"
(super ->string)))))
Every Object has a Class
(define p (new point% [x 1][y 1]))
(define cp (new color-point% [x 1][y 1][color "red"]))
(define y1 (is-a? p object%))
(define y2 (is-a? p point%))
(define n1 (is-a? p color-point%))
(define y3 (is-a? cp object%))
(define y4 (is-a? cp point%))
(define y5 (is-a? cp color-point%))
About that color-point% subclass
In our example, we assume:
Under these assumptions, in this case, subclassing was a good choice, but there are alternatives we should consider
Alternative #1: Edit the superclass
Add to point%:
With default value for init parameter color, we can “almost get away with this”
Problems:
(init [color "black"])
(define current-color color)
(define/public (get-color) …)
(define/public (set-color! c) …)
…
Alternative #2: Copy/paste
Make color-point% a subclass of object% and just copy/paste most of the definition of point%
Note this does basically work:
point%)
But:
(define color-point%
(class object%
(super-new)
(init x y)
(define x-coord x)
(define y-coord y)
(define/public (get-x) x-coord)
(define/public (get-y) y-coord)
(define/public (dist-from-origin) …)
(define/public (dist-from-origin2) …)
(init [color "black"])
(define current-color color)
(define/public (get-color) …)
(define/public (set-color! c) …)
(define/public (->string) …)))
Alternative #3: Use a point% object in private state
Instead of subclassing, could just have a point% “private field” instead!
Implementation has to “forward” method calls to underlying point, i.e., no syntactic convenience of inheritance, but this approach also has flexibility (not needed here)
(define color-point%
(class object%
(super-new)
(init x y)
(define point (new point% [x x][y y]))
(define/public (get-x) (send point get-x)
(define/public (get-y) (send point get-y)
... ; more “forwarding messages”
(init [color "black"])
(define current-color color)
(define/public (get-color) …)
(define/public (set-color! c) …)
(define/public (->string) …)))
Subtleties of Object Initialization
See code for a variant of point% that does (send this ->string) during object initialization
If not careful, a color-point% subclass is broken because:
This is why Racket lets (super-new) appear anywhere in the initialization order
Overall, self calls during object initialization particularly tricky in OOP
What about 3D-Points? (See code)
Having point-3d% subclass point% is highly questionable style
Now Polar-Points
Alternate representation/implementation of the same abstraction
Do not have to subclass, but by doing so a couple fascinating things:
The Key Point re: Overriding
In many cases, objects are not so different from closures:
But overriding methods called by other superclass methods is fundamentally different
Dynamic Dispatch
Now: define the semantics of method resolution precisely
Review: Variable Lookup
To evaluate a variable expression foo:
this
Method Lookup
(send e0 m e1 … eN)
Method Lookup
(send e0 m e1 … eN)
Implements dynamic dispatch!
Punchline
This is why dist-from-origin2 “just worked”
Notes:
“Static Overloading”
In Java, method lookup is slightly more complex:
Java solution:
The OOP Tradeoff
A method m that calls other overridable methods can have its behavior changed in any subclass!
This makes it harder to reason about the code you’re looking at 😬
OTOH, easier for subclasses to customize behavior without copy/pasting code! 😃
Lexical scope does not do that
In functional programming, if an immutable variable is bound to a function in an environment, it is always bound to that function