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>.