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  :
  1. reflexivity
    o1.equals(o1) is always true.
  2. symmetry
    o1.equals(o2) iff o2.equals(o1).
  3. 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)