Point and ColorPoint
This lesson is based on the book "Effective
Java,
Programming
Language
Guide".
It concerns versions 1 to 4.
We consider the following class Point.
We want to add a new class ColorPoint, which makes it
possible to
create points with a color property.
Version 1
In this version, ColorPoint
does not override the method equals(Object). In that
case, it means that two points with different colors are
nevertheless
equals if they have the same coordinates. This is probably not
what you want !
Version 2
In this version, ColorPoint
overrides equals(Object). Now, two color points are
equals iff they have the
same coordinates and the same colors.
Unfortunatly, this solution is wrong !
The equals(Object) method must respect the following
mathematical properties :
- reflexivity
o1.equals(o1) is always true.
- symmetry
o1.equals(o2) iff o2.equals(o1).
- transitivity
if o1.equals(o2) and o2.equals(o3)
are true,
then o1.equals(o3) must be true.
But it is not the case with this implementation, as shown by the main
method of the class Test
:
public static void main(String[] args) {
Point p = new Point(10, 10);
ColorPoint cp = new ColorPoint(10, 10,
Color.RED);
System.out.println(cp.equals(p));
System.out.println(p.equals(cp));
}
The result of the command
> java Test
is
false
true
Thus, the symmetry is not respected.
Version 3
In this version, we just modify the equals(Object)
method
of ColorPoint.
If
we
compare
a
ColorPoint and a Point
which is not an instance of ColorPoint, we don't take
color into account. But this solution also is wrong, as shown by the
new main
method of the class Test
:
public static void main(String[] args) {
Point p = new Point(10, 10);
ColorPoint cp1 = new
ColorPoint(10,10,Color.BLUE);
ColorPoint cp2 = new ColorPoint(10, 10,
Color.RED);
System.out.println(cp1.equals(p));
System.out.println(p.equals(cp2));
System.out.println(cp1.equals(cp2));
}
The result of the command
> java Test
is
true
true
false
Thus the transitivity
is
not respected.
Version 4
The previous versions have proved that an
instance of ColorPoint
cannot be a Point except if we accept that two
instances
of ColorPoint
may be equals with different colors. Thus we propose a new version
of ColorPoint
where we don't use inheritance, but only delegation. Note the method
asPoint()
which makes possible to have a "Point view" of an
instance of ColorPoint.
In that case, it is very easy to implement such a view because an
instance of Point is unmodifiable.
Version 5
We modify Point
in such a way that now, an instance of Point may
be
modified, using the new method moveTo(int x, int y). ColorPoint
is also adapted in the same way.
We
create a new subclass of Point, BoundedPoint,
which
manage
points
with
bounded
coordinates.
If
the
user wants to move a bounded point outside its bounds, the point
remains at its previous coordinates. An IllegalArgumentException
(subclass of the class RuntimeException)
is
thrown
in
the constructor if the bounds do not contain the initial
coordinates. Note that the variable rectangle in BoundedPoint
contains a copy of the rectangle given as parameter in the
constructor,
to avoid modifications of the bounds by modifying the rectangle
given
as parameter outside of the class BoundedPoint.
The Test
class shows that the coordinates of a bounded point are correctly
checked when using the method moveTo(int,
int) on an instance of BoundedPoint. But, if
we
use
the method moveTo(int, int) on the Point
view, the instance of BoundedPoint may be moved
outside
its bounds.
The result of the command
> java Test
is
Current point = (5,5) Bounds :
java.awt.Rectangle[x=0,y=0,width=10,height=10]
Current point = (20,20) Bounds :
java.awt.Rectangle[x=0,y=0,width=10,height=10]
Version 6
To have a better control in BoundedPoint
on the delegation to the instance of Point, we no
longer
use a direct instance of Point, but an instance of an
internal
subclass, called InternalClass.
The class Test
allows to
see that now, all the moves of a bounded point, even using its view
as
point, are properly managed.
The result of the command
> java Test
is
Current point = (5,5) Bounds :
java.awt.Rectangle[x=0,y=0,width=10,height=10]
Current point = (5,5) Bounds :
java.awt.Rectangle[x=0,y=0,width=10,height=10]
Version 7
Now, suppose that we want to replace the beahvior of an instance of
BoundedPoint
: if the new coordinates are out of bounds, we prefer to throw an
exception than leaving the point unchanged. But because BoundedPoint
inherits from Point,
that means that the method move(int, int) in Point
must declare this
exception. Thus, we define a class IllegalMoveException,
and
this
exception
may be thrown by any kind of instance of Point,
ColorPoint or BoundedPoint.
The best way to obtain the expected result is to create a new
interface
Point,
to
rename the former class Point by PointImpl,
and
to
adapt the classes PointImpl, ColorPoint
and BoundedPoint
in such a way that they implement the interface Point.
Note that in that case, the view returned by asPoint()
is
no more necessary as all the instances of both PointImpl,
ColorPoint and BoundedPoint are also
instances of Point, as shown by the class Test.
The result of the command
> java Test
is
Point (5,5) Bounds :
java.awt.Rectangle[x=0,y=0,width=10,height=10] cannot go to
(20,20)
Version 8
In this version, ColorPoint
and BoundedPoint
are decorators. Thus, it is now possible to have colored bounded
points, like in Test.java.