530 likes | 740 Views
Polymorphism. Poly => many Morph => shape Variables take on many shapes, or many classes of objects. Polymorphism. Polymorphism in OOP is caused by late-binding of procedure calls (message lookup). Program can work with any object that has the right set of methods. Object Model for Payroll.
E N D
Polymorphism Poly => many Morph => shape Variables take on many shapes, or many classes of objects. Object-oriented Programming and Design
Polymorphism Polymorphism in OOP is caused by late-binding of procedure calls (message lookup). Program can work with any object that has the right set of methods. Object-oriented Programming and Design
Object Model for Payroll abstract class * EmployeeTransaction * PayrollSystem Employee date postTo: abstract method post: Paycheck SalaryChange salary pay, taxes Timecard hoursWorked vacation Object-oriented Programming and Design
Examples post: aTransaction aTransaction postTo: self. transactations add: aTransaction numbers inject: 0 into: [:sum :i | sum + i] Object-oriented Programming and Design
Many classes with same interface Examples Collections Numbers Magnitude Easier to remember method names Polymorphic clients can use any class Object-oriented Programming and Design
Polymorphic client tasks := OrderedCollection new. tasks add: Task new. [tasks isEmpty] whileFalse: [ task := tasks removeLast. tasks addAll: task computeNewTasks] Object-oriented Programming and Design
Polymorphic client tasks := SortedCollection sortBlock: [:first :second | first priority > second priority]. tasks add: Task new. [tasks isEmpty] whileFalse: [ task := tasks removeLast. tasks addAll: task computeNewTasks] Object-oriented Programming and Design
Polymorphism instead of Case Statements Smalltalk has no case statement. OO programmers tend to replace case statements with message sending. Instead of making a new case, add a subclass. Object-oriented Programming and Design
Eliminating Cases exp case: 1 do: [...]; case: 15 do: [...]. Make classes for 1 and 15, with a method called msg. exp msg or (dictionary at: exp) msg Object-oriented Programming and Design
Class UndefinedObject Class UndefinedObject has one instance -- nil. All variables are initialized to nil. Also used to indicate illegal value. Object-oriented Programming and Design
Don’t Test Classes UndefinedObject Object isNil isNil ^ true ^ false notNil notNil ^ false ^ true Don’t test classes: use message sending and inheritance. Object-oriented Programming and Design
Choices bad x class = UndefinedObject ifTrue: [...] x = nil ifTrue: [...] x isNil ifTrue: [...] x ifNil: […] best Object-oriented Programming and Design
x ifNil: [ … ] Object ifNil: aBlock ^self UndefinedObject ifNil: aBlock ^aBlock value Object-oriented Programming and Design
Magnitude Magnitude () Number () .... Character () Date ('day' 'year') Time ('hours' 'minutes' 'seconds') Object-oriented Programming and Design
Number Hierarchy Number Fraction ('numerator' 'denominator') Integer () LargeNegativeInteger () LargePositiveInteger () SmallInteger () ... Object-oriented Programming and Design
Numbers Numbers are part of the class hierarchy, not built into compiler. Numbers understand +, -, *, /, <, <=, etc. 3 / 6 => 1 / 2 2 sqrt => 1.41421 3 + 4 * 2 => Object-oriented Programming and Design
Numbers are polymorphic (3/4) * 0.45 + 4.6s2 4.9375 (0.45 + 4.6s2) / 17 0.297059 (0.45 + 4.6s2) truncated / 17 (5/17) Object-oriented Programming and Design
Polymorphism, not conditionals Don’t explicitly check class of argument. Use polymorphism to make computation depend on class of argument. aThing class = A ifTrue: [self doStuffWith: aThing] ifFalse: [self doSomethingElseWith: aThing] Object-oriented Programming and Design
Original in Number raisedTo: aNumber "Answer the receiver raised to aNumber." aNumber isInteger ifTrue: ["Do the special case of integer power" ^ self raisedToInteger: aNumber]. self < 0 ifTrue: [ self error: self printString, ' raised to a non-integer power' ]. aNumber = 0 ifTrue: [^ 1]. "Special case of exponent=0" (self = 0) | (aNumber = 1) ifTrue: [^ self]. "Special case of exponent=1" ^ (aNumber * self ln) exp "Otherwise use logarithms" Object-oriented Programming and Design
Original in Number raisedToInteger: operand "Answer the receiver raised to the power operand, an Integer." | count result | operand = 0 ifTrue: [^ self class one]. operand = 1 ifTrue: [^ self]. operand < 0 ifTrue: [^ (self raisedToInteger: operand negated) reciprocal]. count := 1. [(count := count + count) < operand] whileTrue. result := self class one. [count > 0] whileTrue: [result := result * result. (operand bitAnd: count) = 0 ifFalse: [result := result * self]. count := count bitShift: -1]. ^ result Object-oriented Programming and Design
Refactoring raisedTo: raisedTo: power "Answer the receiver raised to a power." self = 1 ifTrue: [^ self]. ^ power timesMultiply: self Object-oriented Programming and Design
Integer timesMultiply: aNumber | count result | self = 0 ifTrue: [^ aNumber class one]. self < 0 ifTrue: [^ (self negated timesMultiply: aNumber) reciprocal]. count := 1. [(count := count + count) < self] whileTrue. result := 1. [count > 0] whileTrue: [result := result * result. (self bitAnd: count) = 0 ifFalse: [result := result * aNumber]. count := count bitShift: -1]. ^ result Object-oriented Programming and Design
Number timesMultiply: aNumber | truncated | aNumber < 0.0 ifTrue: [(truncated := self truncated) = self ifTrue: ["self is a whole number." ^ (truncated timesMultiply: aNumber) * self class one]. ArithmeticError signal: aNumber printString , ' : negative number raised to a non-integer power']. ^ (self * aNumber ln) exp Object-oriented Programming and Design
Double-dispatching: the Problem Fl Fr Fi M * Fl Fr Fi Sc Smallinteger Fl * Fl Fl Sc Float Fr Fl * Fi Sc Fraction Fi Fl Fi Sc * FixedPoint Sc Sc Sc Sc Matrix *
Arithmetic Number * Matrix Multiply each element by number Matrix * Number Multiply each element by number Matrix * Matrix Standard matrix multiplication
Arithmetic Integer * Integer Primitive int operations Integer * Float Convert int to float Float * Integer Convert int to float Float * Float Primitive float operation
Double dispatching: the Solution • Primary method (+, *, etc) sends a second message to argument, encoding the class of the receiver. • Second message knows class of both its argument and its receiver.
Primary operations Send a second message, encoding the class of the original receiver in the name of the message. + anArg anArg sumFromInteger: self
Double Dispatching Methods Knows the class of receiver and argument. sumFromInteger: anInteger ^anInteger asFloat + self
The first message dispatch 37 + 8.9 SmallInteger+ anArg <primitive: 1> ^ anArg sumFromInteger: self 8.9 sumFromInteger: 37
The second message dispatch 8.9 addSmallInteger: 37 FloataddSmallIngeter: anArg ^ anArg asFloat + self 8.9 + 37.0
Finishing Up 8.9 + 37.0 Float+ anArg <primitive: 41> ^aNumber sumFromFloat: self
Multiplication Fraction has two instance variables, numerator and denominator. * aNumber "Result is a new Fraction unless the argument is a Float, in which case the result is a Float." ^aNumber productFromFraction: self
Fraction productFromFraction: aFraction ^(self species numerator: aFraction numerator * numerator denominator: aFraction denominator * denominator) reduced
Integer productFromFraction: aFraction ^(aFraction species numerator: aFraction numerator * self denominator: aFraction denominator) reduced
Float productFromFraction: aFraction ^aFraction asFloat * self
Float * aNumber "Answer a Float that is the result of multiplying the receiver by the argument, aNumber. The primitive fails if it cannot coerce the argument to a Float" <primitive: 49> ^aNumber productFromFloat: self
Fraction productFromFloat: aFloat ^aFloat * self asFloat
Shared Responsibilities Sometimes need to select a method based on class of several objects: Displaying object -- depends on both the kind of object and the windowing system Arithmetic -- depends on the types of both arguments
Double dispatching Three kinds of mesages • primary operations • double dispatching methods • forwarding operations Implement inheriting from superclass of argument Implement commutativity
Cost of Double Dispatching Adding a new class requires adding a message to each of the other classes. Worst case is N*N methods for N classes. However, total lines of code is not much larger.
openMessageEditString: aString "Create a pluggable version of the views for a Browser that just shows one message." | messageListView browserCodeView topView annotationPane underPane y | Smalltalk isMorphic ifTrue: [^ self openAsMorphMessageEditing: aString]. topView := (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" messageListView := PluggableListView on: self list: #messageListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageListMenu:shifted:. messageListView window: (0 @ 0 extent: 200 @ 12). topView addSubView: messageListView. … Object-oriented Programming and Design
openOnClassWithEditString: aString "Create a pluggable version of all the views for a Browser, including views and controllers." | classListView messageCategoryListView messageListView browserCodeView topView switchView annotationPane underPane y optionalButtonsView | Smalltalk isMorphic ifTrue: [^ self openAsMorphClassEditing: aString]. topView := (StandardSystemView new) model: self. topView borderWidth: 1. "label and minSize taken care of by caller" classListView := PluggableListView on: self list: #classListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #classListMenu:shifted: keystroke: #classListKey:from:. classListView window: (0 @ 0 extent: 100 @ 12). topView addSubView: classListView. Object-oriented Programming and Design
Problem • Two user interface frameworks, Morphic and MVC • Application knows its UI frameworks • Refactor so app doesn’t know its UI Object-oriented Programming and Design
Solution Have method delegate to an object that might be either a Morphic expert or a MVC expert openMessageEditString: aString ^UIManager default openMessageEditString: aString for: self Object-oriented Programming and Design
MorphicUIManager openMessageEditString: aString for: aBrowser ^aBrowser openAsMorphMessageEditing: aString Object-oriented Programming and Design
MVCUIManager openMessageEditString: aString for: aBrowser | topView messageListView … | topView := (StandardSystemView new) model: aBrowser. topView borderWidth: 1. "label and minSize taken care of by caller" messageListView := PluggableListView on: aBrowser list: #messageListSingleton selected: #indexIsOne changeSelected: #indexIsOne: menu: #messageListMenu:shifted:. messageListView window: (0 @ 0 extent: 200 @ 12). topView addSubView: messageListView. Object-oriented Programming and Design
First attempt To get rid of isMorphic Change method to delegate to UIManager default Copy old method into MVCUIManager and MorphicUIManager Replace “isMorphic” with “true” or “false” Simplify Object-oriented Programming and Design
Problem UIManager subclasses will accumulate application-specific code. Methods related to application should be in application package. Object-oriented Programming and Design
Uses of Polymorphism Methods often depend radically on class of receiver. isNil ifTrue:ifFalse: double dispatching Object-oriented Programming and Design