[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: OO should break when broken(was re: the benefits of immutability)
On Sunday, August 31, 2003, at 10:20 PM, Michael St . Hippolyte wrote:
> A Circle class implies a class hierarchy defined by shape,
> and it's not clear to me what kind of shape you could
> meaningfully subclass from Circle; what can you change
> geometrically about a circle without making it not a
> circle? On the other hand, geometrically a circle is a
> special case of ellipse. Clearly, any geometrically valid
> test for equality between ellipses would work on circles as
> well, so there shouldn't be any need for Circle to override
> the equals method, and if it does override equals (for
> efficiency reasons perhaps) it should yield the same
> outcome.
Not only do you not need to override equals, you don't need to subclass
at all. Anytime you need a circle, just create an ellipse that just
happens to be circular.
Of course, it still might make sense to have two classes, depending on
the application. But if so, Circle should be the superclass. A subclass
should extend its superclass, not constrain it. An ellipse is a circle
with additional capabilities - it can have different radii along each
of its dimensions.
It's ironic that shapes are so often used as examples in discussions of
OO modelling. In fact, they make terrible examples, because geometry
produces subtypes in exactly the opposite way that class-based object
modelling does.
Getting back to the ColoredCircle example, I think the problem is that
equality is only really applicable to certain mathematical concepts,
and is meaningless when applied to things like colored circles. Here's
how I'd model it. (Sorry, I don't do Java, so here's the Smalltalk
version. For those that don't read Smalltalk, I basically have #=
return false if objects of different classes are compared. I use
#isCongruentTo: for testing geometrical equality)
Object subclass: #Circle
instanceVariableNames: 'radius '
classVariableNames: ''
poolDictionaries: ''
category: 'Example'!
!Circle methodsFor: 'accessing'!
radius
^ radius! !
!Circle methodsFor: 'accessing'!
radius: anInteger
radius := anInteger! !
!Circle methodsFor: 'comparing'!
= other
^ self class = other class and: [self isCongruentTo: other]! !
!Circle methodsFor: 'comparing'!
isCongruentTo: other
^ self radius = other radius! !
Circle class
instanceVariableNames: ''!
!Circle class methodsFor: 'as yet unclassified'!
radius: anInteger
^ self new radius: anInteger! !
Circle subclass: #ColoredCircle
instanceVariableNames: 'color '
classVariableNames: ''
poolDictionaries: ''
category: 'Example'!
!ColoredCircle methodsFor: 'accessing'!
color
^ color! !
!ColoredCircle methodsFor: 'accessing'!
color: aColor
color := aColor! !
!ColoredCircle methodsFor: 'comparing'!
= other
^ super = other and: [self color = other color]! !
!ColoredCircle methodsFor: 'comparing'!
isSameColor: other
^ self color = other color! !
ColoredCircle class
instanceVariableNames: ''!
!ColoredCircle class methodsFor: 'as yet unclassified'!
radius: anInteger color: aColor
^ self new radius: anInteger; color: aColor! !
TestCase subclass: #CircleTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Example'!
!CircleTest methodsFor: 'testing'!
testCongruence
| five four fiveRed fourRed fiveBlue |
five := Circle radius: 5.
four := Circle radius: 4.
fiveRed := ColoredCircle radius: 5 color: Color red.
fourRed := ColoredCircle radius: 4 color: Color red.
fiveBlue := ColoredCircle radius: 5 color: Color blue.
self assert: (five isCongruentTo: five).
self assert: (fiveRed isCongruentTo: fiveRed).
self assert: (fiveRed isCongruentTo: fiveBlue).
self assert: (five isCongruentTo: fiveRed).
self assert: (fiveRed isCongruentTo: five).
self deny: (five isCongruentTo: four).
self deny: (fiveRed isCongruentTo: fourRed).
! !
!CircleTest methodsFor: 'testing'!
testEquality
| five four fiveRed fourRed fiveBlue |
five := Circle radius: 5.
four := Circle radius: 4.
fiveRed := ColoredCircle radius: 5 color: Color red.
fourRed := ColoredCircle radius: 4 color: Color red.
fiveBlue := ColoredCircle radius: 5 color: Color blue.
self assert: (five = five).
self assert: (fiveRed = fiveRed).
self deny: (five = four).
self deny: (fiveRed = fourRed).
self deny: (fiveRed = fiveBlue).
self deny: (five = fiveRed).
self deny: (fiveRed = five).! !