Generic interfaces and algorithms

A common pattern in object oriented programming is interfacing classes that have the same methods. If these classes encapsulate a datatype and expose  methods that take the datatype as a parameter, generic interfaces. The approach results in the ability to apply a generic algorithm on different encapsulated data types.

For example vector math classes have this property, they can be defined for double and float datatypes and also for 2- and 3-dimensional implementations with methods for doing common operations.

public final class Vec3 implements Vec<Vec3> {
  private double x;
  private double y;
  private double z;

  /**
   * returns the distance to another vector
   * @param other the other vector
   * @return distance
   */
   public final double distance(Vec3 other) {
      return Math.sqrt(
        (x-other.x)*(x-other.x)+
        (y-other.y)*(y-other.y)+
        (z-other.z)*(z-other.z));
   }
}

public final class Vec2 implements Vec<Vec2> {
  private double x;
  private double y;
 
  /**
   * returns the distance to another vector
   * @param other the other vector
   * @return distance
   */
   public final double distance(Vec2 other) {
      return Math.sqrt(
        (x-other.x)*(x-other.x)+
        (y-other.y)*(y-other.y));
   }
}

The generic interface that both classes implement can then be declared like this:

public interface Vec<V> {
   double distance(V other);
}

It can contain all functions that have the same ‘abstract’ method signature that the classes have in common, ie. the more complicated cross product that returns a vector again.

The real benefit of this is not immediately obvious, consider adding up the distances of a list of items – one would naively just implement the method for each vector type Vec2 and Vec3. This results in duplicate code, reduced maintainability and is errorprone.

A class holding the algorithms operating on the Vec<V> interface can be used instead, the method needs to make use of the disance(V) method to determine the overall distance.

public class Toolbox {
  /**
   * Determines the overall distance between the points in the list
   */
  public static <T extends Vec<T>> double getAllDistances(List<T> points) {
     T prevPoint = points.get(0);
     T point;
     double sum = 0.0;
     int len = points.size();
     for (int i=1;i<len; i++) {
        point = points.get(i);
        sum+=point.distance(prevPoint);
        prevPoint = point;
     }
     return sum;
  }
}

This method is type-safe and works with all types implementing the generic Vec interface due to the <T extends Vec<T>> declaration. That way algorithms on vectors can safely be defined once for several variations of the classes.

In contrast, a declaration like below seems more natural at first, but does not work:

 public static <T> double getAllDistances(List<Vec<T>> points) {
    Vec<T> prevPoint = points.get(0);
    Vec<T> point;
    double sum = 0.0;
    for (int i=1; i<points.size(); i++) {
       point = points.get(i);
       sum+=point.distance(prevPoint);
       prevPoint = point;
    }
    return sum;
 }

Here, calling the method point.distance(prevPoint) gives a compiler error since prevPoint is of type Vec<T> but the method signature is distance(V other) and expects it to be T, rather than Vec<T>.