Date post: | 03-Jan-2016 |
Category: |
Documents |
Upload: | sabina-bradley |
View: | 226 times |
Download: | 0 times |
Object-oriented Programming and Design
1
Polymorphism
Poly => many
Morph => shape
Variables take on many shapes, or many classes of objects.
Object-oriented Programming and Design
2
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
3
Object Model for Payroll
EmployeeEmployeeTransaction
PayrollSystem
post:
date
Paycheck
Timecard
SalaryChangesalarypay, taxes
hoursWorkedvacation
postTo::
abstract classabstract class
abstract methodabstract method
** **
Object-oriented Programming and Design
4
Examples
post: aTransaction
aTransaction postTo: self.
transactations add: aTransaction
numbers inject: 0 into: [:sum :i | sum + i]
Object-oriented Programming and Design
5
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
6
Polymorphic client
tasks := OrderedCollection new.
tasks add: Task new.
[tasks isEmpty]
whileFalse: [
task := tasks removeLast.
tasks addAll: task computeNewTasks]
Object-oriented Programming and Design
7
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
8
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
9
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
10
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
11
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
12
Choices
bad
x class = UndefinedObject ifTrue: [...]
x = nil ifTrue: [...]
x isNil ifTrue: [...]
x ifNil: […]
best
Object-oriented Programming and Design
13
x ifNil: [ … ]
Object
ifNil: aBlock
^self
UndefinedObject
ifNil: aBlock
^aBlock value
Object-oriented Programming and Design
14
MagnitudeMagnitude ()
Number ()
....
Character ()
Date ('day' 'year')
Time ('hours' 'minutes' 'seconds')
Object-oriented Programming and Design
15
Number Hierarchy
NumberFraction ('numerator' 'denominator')
Integer ()
LargeNegativeInteger ()
LargePositiveInteger ()
SmallInteger ()
...
Object-oriented Programming and Design
16
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
17
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
18
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
19
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
20
Original in NumberraisedToInteger: 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
21
Refactoring raisedTo:
raisedTo: power
"Answer the receiver raised to a power."
self = 1 ifTrue: [^ self].
^ power timesMultiply: self
Object-oriented Programming and Design
22
IntegertimesMultiply: 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
23
NumbertimesMultiply: 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
Double-dispatching: the Problem
FloatFloat
SmallintegerSmallinteger
FractionFraction
FixedPointFixedPoint
MatrixMatrix
FrFr
ScSc
FiFi
FlFl MM
**
**
**
**
**
FlFl
FlFl
FlFl
FlFlFlFl
FrFr
FrFr
FlFl
FiFi
FiFi
ScSc
ScSc
ScSc ScSc
ScSc
ScScScSc
FiFi
FiFi
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
Float addSmallIngeter: anArg
^ anArg asFloat + self
8.9 + 37.0
MultiplicationFraction 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
* 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
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.
Object-oriented Programming and Design
42
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: selflist: #messageListSingletonselected: #indexIsOne changeSelected: #indexIsOne:menu: #messageListMenu:shifted:.
messageListView window: (0 @ 0 extent: 200 @ 12).topView addSubView: messageListView.
…
Object-oriented Programming and Design
43
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: selflist: #classListSingletonselected: #indexIsOne changeSelected: #indexIsOne:menu: #classListMenu:shifted:keystroke: #classListKey:from:.
classListView window: (0 @ 0 extent: 100 @ 12).topView addSubView: classListView.
Object-oriented Programming and Design
44
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
45
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
46
MorphicUIManager
openMessageEditString: aString for: aBrowser
^aBrowser openAsMorphMessageEditing: aString
Object-oriented Programming and Design
47
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: aBrowserlist: #messageListSingletonselected: #indexIsOne changeSelected: #indexIsOne:menu: #messageListMenu:shifted:.
messageListView window: (0 @ 0 extent: 200 @ 12).topView addSubView: messageListView.
Object-oriented Programming and Design
48
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
49
Problem
UIManager subclasses will accumulate application-specific code.
Methods related to application should be in application package.
Object-oriented Programming and Design
50
Uses of Polymorphism
Methods often depend radically on class of receiver.
isNil
ifTrue:ifFalse:
double dispatching
Object-oriented Programming and Design
51
Double dispatch
Sometimes method needs to depend on class of receiver AND class of argument
Solution: First method sends message to argument,
encoding type of original receiver in the message name
Second method knows type of both receiver and argument
Object-oriented Programming and Design
52
Double dispatch in browser
Browser>>displayWith: aUIManager
aUIManager displayBrowser: self
MorphicUIManager>>displayBrowser: aBrowser
…