[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).! !