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
.