Case Study: The Number Hierarchy Goal: Develop feel for programming in the large Issues we’ll consider along the way: • Class design – What methods go where? • Invariants – Class methods establish invariants – Instance methods must maintain them • Information hiding – “semi-private” methods for like objects – Double-dispatch when argument form matters.
The Number Hierarchy Object Magnitude Number Integer Fraction Float
Instance protocol for Magnitude equality (like Magnitudes) = aMagnitude comparison (ditto) < aMagnitude comparison (ditto) > aMagnitude <= aMagnitude comparison (ditto) comparison (ditto) >= aMagnitude minimum (ditto) min: aMagnitude maximum (ditto) max: aMagnitude Subclasses: Date , Natural • Compare Date with Date , Natural w/ Natural , . . .
Implementation of Magnitude : Reuse (class Magnitude ; abstract class [subclass-of Object] (method = (x) (self subclassResponsibility)) ; may not inherit = from Object (method < (x) (self subclassResponsibility)) (method > (y) (y < self)) (method <= (x) ((self > x) not)) (method >= (x) ((self < x) not)) (method min: (aMag) ((self < aMag) ifTrue:ifFalse: {self} {aMag})) (method max: (aMag) ((self > aMag) ifTrue:ifFalse: {self} {aMag})) )
The Number Hierarchy, Reprise Object Magnitude Number Integer Fraction Float
Instance protocol for Number negated reciprocal absolute value abs addition + aNumber - aNumber subtraction multiplication * aNumber division (may answer a Fraction !) / aNumber isNegative sign check sign check isNonnegative sign check isStrictlyPositive coerce: aNumber class of receiver, value of argument conversion asInteger conversion asFraction conversion asFloat
Object-oriented design Given that class Number inherits from class Magnitude , which of these methods of class Number can be implemented in terms of others? negated coerce: * reciprocal / asInteger abs isNegative asFraction + isNonnegative asFloat - isStrictlyPositive
Concrete Number Methods (method - (y) (self + (y negated))) (method abs () ((self isNegative) ifTrue:ifFalse: {(self negated)} {self})) (method / (y) (self * (y reciprocal))) (method isNegative () (self < (self coerce: 0))) (method isNonnegative () (self >= (self coerce: 0))) (method isStrictlyPositive () (self > (self coerce: 0)))
Abstract Number Methods (class Number [subclass-of Magnitude] ; abstract class ;;;;;;; arithmetic (method + (aNumber) (self subclassResponsibility)) (method * (aNumber) (self subclassResponsibility)) (method negated () (self subclassResponsibility)) (method reciprocal () (self subclassResponsibility)) ;;;;;;; coercion (method asInteger () (self subclassResponsibility)) (method asFraction () (self subclassResponsibility)) (method asFloat () (self subclassResponsibility)) (method coerce: (aNumber) (self subclassResponsibility)) )
Extended Number Hierarchy Object Magnitude Natural Number Integer Fraction Float SmallInteger LargeInteger LargePositiveInteger LargeNegativeInteger
Extended Number Hierarchy Object Magnitude Natural Number Integer Fraction Float SmallInteger LargeInteger LargePositiveInteger LargeNegativeInteger
Example class Fraction: Initialization (class Fraction [subclass-of Number] [ivars num den] ;; invariants: lowest terms, minus sign on top ;; if num = 0, den = 1 ;; maintained by divReduce, signReduce (class-method num:den: (a b) ((self new) initNum:den: a b)) (method initNum:den: (a b) ; private (self setNum:den: a b) (self signReduce) (self divReduce)) (method setNum:den: (a b) (set num a) (set den b) self) ; private .. other methods of class Fraction ... )
Information revealed to self Instance variables num and den • Directly available • Always and only go with self Object knows its own representation, invariants, private methods: (method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce))
Information revealed to others: coerce How would you implement coerce: ? (Value of argument, representation of receiver) (method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce)) (method coerce: (aNumber) ...)
Information revealed to others: coerce How would you implement coerce: ? • Value of argument, rep of receiver • Challenge: Can’t access rep of argument! (method asFraction () self) (method print () (num print) (’/ print) (den print) self) (method reciprocal () (((Fraction new) setNum:den: den num) signReduce)) (method coerce: (aNumber) (aNumber asFraction)) Saved: Number protocol includes asFraction !
The Number Hierarchy, Reprise How to implement comparisons on Fraction s? Object Magnitude Number Integer Fraction Float
Instance protocol for Magnitude , Reprise How to implement comparisons on Fraction s? equality (like Magnitudes) = aMagnitude comparison (ditto) < aMagnitude > aMagnitude comparison (ditto) comparison (ditto) <= aMagnitude comparison (ditto) >= aMagnitude minimum (ditto) min: aMagnitude maximum (ditto) max: aMagnitude Subclasses: Date , Natural • Compare Date with Date , Natural w/ Natural , . . .
Implementing comparison for Fraction s Alas! Cannot see representation of argument How will you know “equal, less or greater”?
Implementing comparison for Fraction s Alas! Cannot see representation of argument Protocol says “like with like”? Extend the protocol (method num () num) ; extension, semi-private (method den () den) ; extension, semi-private (method = (fr) ((num = (fr num)) and: {(den = (fr den))})) (method < (fr) ((num * (fr den)) < ((fr num) * den)))
Extended protocol: Multiply two fractions How will you multiply two fractions?
Extending the protocol to multiply fractions How will you multiply two fractions? (method * (aFraction) (((Fraction new) setNum:den: (num * (aFraction num)) (den * (aFraction den))) divReduce))
Extending open systems Number protocol: like multiplies with like Goal: • Large integers and small integers are both Integer s • Messages = , < , + , * ought to mix freely Constraint: Each object has its own algorithm. • Small: Use machine-primitive multiplication • Large: Multiply magnitudes; choose sign Double dispatch to the rescue!
Double dispatch: Algebraic laws Laws of multiplication: (:+ n) * (:- m) == :- (n * m) (:+ n) * (:+ m) == :+ (n * m) (:+ n) * small == (:+ n) * (small asLargeInteger) But! Can’t distinguish forms of argument Solution: “Dispatch laws” (:+ n) * (:- m) == ((:- m) timesLP: self) (:+ n) * (:+ m) == ((:+ m) timesLP: self) (:+ n) * small == (small timesLP: self) Argument to timesLP: • Understands “large positive integer” protocol
Double dispatch methods encode operation & protocol Example messages: • timesLP: arg arg answers the large-positive integer protocol receiver should multiply itself by arg • plusSP: arg arg answers the small-integer protocol receiver should add itself to arg Message encodes • Operation to be performed • Protocol accepted by argument
Double dispatch to implement addition How do you act? • As small integer, you’re asked to add large positive integer N to self (aka plusLP: N ) Response: ((self asLargeInteger) plus N) • As small integer, you’re asked to add small integer n to self (aka plusSP: n ) Response: (self plus n) • As large positive integer, you’re asked to add large positive integer N to self (aka plusLP: N ) Response: (self plus n) • As large positive integer, you’re asked to add small integer n to self (aka plusSP: n ) Response: (self plus (n asLargeInteger))
Your turn: Double dispatch to implement multiplication How do you act? • As small integer, you’re asked to multiply large positive integer N by self (aka timesLP: N ) Response: ((self asLargeInteger) * N) • As small integer, you’re asked to multiply small integer n by self (aka timesSP: n ) Response: (self * n) • As large positive integer, you’re asked to multiply large positive integer N by self (aka timesLP: N ) Response: (self * n) • As large positive integer, you’re asked to multiply small integer n by self (aka timesSP: n ) Response: (self * (n asLargeInteger))
Your turn: Understanding double dispatch On what class does each method go? A. (method + (aNumber) (aNumber addSmallIntegerTo: self)) B. (method * (anInteger) (anInteger multiplyByLargePositiveInteger: self))
Review: Two kinds of knowledge I can send message to you: • I know your protocol I can inherit from you: • I know my subclass responsibilities
Knowledge of protocol Three levels: • I know only your public methods Example: send select: to any collection • You are like me: share semi-private methods Example: send * or + to Fraction • I must get to know you: double dispatch Example: send * to + to any integer
Extra: Dealing with overflow New law for multiplication: (self * small) = ((primitive mulWithOverflow self small {((self asLargeInteger) * small)}) value)) Primitive is not a method • Answers good block or exception block • Answer is then sent value
Recommend
More recommend