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
Example class Fraction: Initialization (class Fraction [subclass-of Number] [ivars num den] ;; invariants: lowest terms, minus sign on top ;; 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 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) ...) Saved: Number protocol includes asFraction !
Information revealed to others 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) + N) • As small integer, you’re asked to add small integer n to self (aka plusSP: n ) Response: (self + n) • As large positive integer, you’re asked to add large positive integer N to self (aka plusLP: N ) Response: (self + n) • As large positive integer, you’re asked to add small integer n to self (aka plusSP: n ) Response: (self * (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