CSC 322.01, Class 39: Rethinking object-oriented design (6)
Overview
- Preliminaries
- Notes and news
- Upcoming work
- Extra credit
- Questions
- Inheritance vs. mixins
- Issues with multiple inheritance
- A detour
- Close code reading
- Another detour
- Exercise
Preliminaries
News / Etc.
- Do we need mentor sessions next week? Probably not.
- Please invite your community partners to our final presentations at 2:00 p.m. on Friday, 11 May 2018. (Our normal classroom.)
- I responded to all of the journals that were submitted by 7:30 p.m.
- Monday’s class: Work on presentations.
- Wednesday’s class: EOCEs and debrief (and work on presentations)
- Advice will be useful
- Friday’s class: Presentations
Upcoming work
- No more readings.
- This week is your last week of reports.
- Presentations a week from today.
- About ten minutes + five minutes for Q&A.
- Make sure that your computer works in this class room or that you can use the computer that is in the room..
- Final portfolios due end of finals week.
- Project instructions due end of finals week.
Good things to do (Academic/Artistic)
- Public science symposium TODAY
- Up-goer five posters TODAY
- Up-goer five poster reception 3pm TODAY (we’ll go)
- Talk TODAY at 4:00 by Minute Physics Alum Celebs
Good things to do (Peer)
- The Grinnellian Festival TOMORROW on commencement stage. Your classmates are probably performing at about 5:30 p.m.
Good things to do (Misc)
- Senior week stuff
- Waltz
Less good things to do (Misc)
- Alice
- Rocky Horror Picture Show
Friday PSA
- Please be moderate
- The bubble is contracting; be safe
- Don’t damage other people; consent is essential
Questions
Inheritance vs. Mixins
I was interested to see that many of you approached this question conceptually, in terms of what and how you can model. I was actually considering the issue more in terms of what it gives you as programmer.
- A lot of OOD is finding ways to keep (make) your code DRY
- Also maintainable, reusable, …
- Both provide mechanisms to automatically delegate method calls to
something else. (Effectively “inherit methods”)
- I believe both support both instance and class methods, but I have not verified that carefully.
- Class methods are the static methods in Java. They are associated with the class as a whole rather than individual objects.
- Instance methods are associated with individual objects.
Integer.parse(String)vs5.square()
- Inheritance also permits you to inherit fields. (As far as I can tell, mixins do not normally include fields, probably for some reasons we consider below.)
- You can have multiple mixins but at most one superclass.
- You can explicitly delegate a method call to a superclass, even if you’ve
overridden it.
- It is much more complicated to delegate a method call to a mixin.
class A
def initialize(args)
puts "Initializing A"
end
def speak
"eh"
end
end
class B < A
def initialize (args)
puts "Initializing B"
super
end
def speak
"be"
end
# ...
def something
# I want to call the speak method from my superclass
A.speak #no
end
end
Issues with multiple inheritance
A central issue: What happens when both classes have a method with the same name but different implementations.
class Super
def flies
true
end
end
class Uber
def flies
["fruit", "horse"]
end
end
class Sub < Super, Uber
end
What happens when you do something like the following
s = Sub.new
s.flies
Worse yet, what happens when field names overlap/collide?
In Java
public class Point {
double x;
double y;
public void hshift(double amt) {
x += amt;
}
}
public class AlphabetEncoding {
int a = 1;
int b = 2;
...
int x = 24;
int y = 25;
int z = 26;
}
public class Insane extends Point extends AlphabetEncoding {
...
}
class Point
attr_reader :x, :y
def initialize
@x = 0
@y = 0
end
def hshift(amt)
@x += amt
end
end
class AlphabetEncoding
attr_reader :a, :b, :c, ...., :x, :y, :z
def initialize
@a = 1;
@b = 2;
...
@x = 24;
@y = 25;
@z = 26;
end
end
class Stupid < Point, AlphabetEncoding
end
Do mixins help with those kinds of issues? If so, how?
module Tom
def describe
"American Actor"
end
end
module Chex
def describe
["wheat chex", "pretzels", "bagel chips", "extra gluten"]
end
end
class Mixup
include Tom
include Chex
end
What happens with Mixup.new.describe?
- In Ruby, there are clear priorities. The most recently added mixin (module) gets priority.
- Mixins are less the solution than a clear policy on what it means to mixin multiple classes.
Detour
Note that we might have chosen to define the silly AlphabeticEncoding
class as follows.
class AlphabetEncoding
def valid(sym)
sym.length == 1 and "a" <= sym.to_s and sym.to_s <= "z"
end
def respond_to_missing?(method)
valid method
end
def method_missing(method, *args, &block)
if valid method
1 + (method.to_s.ord - "a".ord)
else
super
end
end
end
Close reading
Few of you read the text as closely as I wanted. Here’s the code we started with, adapted from https://github.com/skmetz/poodr/blob/master/chapter_7.rb
class Schedule
def scheduled?(schedulable, start_date, end_date)
puts "This #{schedulable.class} " +
"is not scheduled\n" +
" between #{start_date} and #{end_date}"
false
end
end
module Schedulable
attr_writer :schedule
def schedule
@schedule ||= ::Schedule.new
end
def schedulable?(start_date, end_date)
!scheduled?(start_date - lead_days, end_date)
end
def scheduled?(start_date, end_date)
schedule.scheduled?(self, start_date, end_date)
end
# includers may override
def lead_days
0
end
end
class Vehicle
include Schedulable
def lead_days
3
end
# ...
end
class Mechanic
include Schedulable
def lead_days
4
end
# ...
end
require 'date'
starting = Date.parse("2015/09/04")
ending = Date.parse("2015/09/10")
v = Vehicle.new
v.schedulable?(starting, ending)
# Note: schedulable? calls scheduled with self as the first parameter,
# which calls scheduled in the Schedule class, which takes
# start_date and end_date as parameters.
# puts "This #{schedulable.class} " +
# "is not scheduled\n" +
# " between #{start_date} and #{end_date}"
# I expect to see
# This Vehicle is not scheduled
# between 2015-09-01 and 2015-09-10
# Metz shows us
# This Vehicle is not scheduled
# between 2015-09-01 and 2015-09-10
# => true
m = Mechanic.new
m.schedulable?(starting, ending)
# I expect to see
# This Mechanic is not scheduled
# between 2015-08-31 and 2015-09-10
# This Mechanic is not scheduled
# between 2015-02-29 and 2015-09-10
# Whoops! There wasn't a February 29, 2015
# => true
How are the two outputs different?
Why are the two outputs different?
Why might I want you to read the outputs closely?
- You learn from doing so.
Another detour
module Schedulable
attr_writer :schedule
What does the attr_writer line in the Schedulable module do?
- Gives you a setter for the
schedulefield in anything that imports Schedulable.
Why does Metz include it?
- I’m not sure. It seems dangerous to me.
Why not use attr_accessor?
- That provides a getter and setter.
- Perhaps we don’t want the client to get the schedule. If the client can get the schedule, the client might decide to work directly with the schedule, rather than through the interface that our object provides. We want to add lead days.
- Or perhaps because we’re already defining a
schedulemethod.
module Mutate
attr_writer :x
end
class Thing
include Mutate
end
t = Thing.new
t.x = 5 # Okay, even if not the intent of THing
t.y = 5 # Not okay, not defined
t.x # Not okay, not defined
t.y # Not okay, not defined
An Exercise
Metz started the chapter with a question about how we’d handle Recumbent Mountain Bikes. But she never quite answers it. So it’s our job. (And no, most of you did not do a sufficient job.)
Review
Here’s the code we started with, adapted from https://github.com/skmetz/poodr/blob/master/chapter_6.rb
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args={})
@size = args[:size]
@chain = args[:chain] || default_chain
@tire_size = args[:tire_size] || default_tire_size
post_initialize(args)
end
def spares
{ tire_size: tire_size,
chain: chain}.merge(local_spares)
end
def default_tire_size
raise NotImplementedError
end
# subclasses may override
def post_initialize(args)
nil
end
def local_spares
{}
end
def default_chain
'10-speed'
end
end
class RoadBike < Bicycle
attr_reader :tape_color
def post_initialize(args)
@tape_color = args[:tape_color]
end
def local_spares
{tape_color: tape_color}
end
def default_tire_size
'23'
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def post_initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
def local_spares
{rear_shock: rear_shock}
end
def default_tire_size
'2.1'
end
end
class RecumbentBike < Bicycle
attr_reader :flag
def post_initialize(args)
@flag = args[:flag]
end
def local_spares
{flag: flag}
end
def default_chain
'9-speed'
end
def default_tire_size
'28'
end
end
What do we see as differences between the various kinds of bikes?
The big question
How do we model all of this when we may have recumbent mountain bikes? What classes and modules would you create?
And another set of notes for Sam
These are my attempt to determine whether I can choose which module’s method to call when there’s a name overlap.
module A
def comment
'#{@user} says "eh"'
end
end
module B
def comment
'Where can #{@user} be?'
end
end
class C
include A
include B
def initialize(name = "Sam")
@user = name
end
def both
A::instance_method(:comment).bind(self).call + "\n" +
B::instance_method(:comment).bind(self).call
end
end
module Flag
def post_initialize(args)
puts "Flag"
end
def announce
"Flag me down!"
end
end
module Shocks
def post_initialize(args)
puts "Shocks"
end
def announce
"So shocking!"
end
end
class Bicycle
def initialize(args)
# Get the list of included modules (which I'm calling the parts)
@parts = self.class.included_modules
@parts.delete(Kernel)
@parts.each do |part|
if part.instance_methods.include?(:post_initialize) then
part.instance_method(:post_initialize).bind(self).call(args)
end
end
end
# Override!
def local_parts
[]
end
# Override
def post_initialize(args)
end
end
class RecumbentMountainBike < Bicycle
include Flag
include Shocks
end