Goals:
When the number of strategy changes, we want to minimize change. When the implementation of a strategy changes, we want to minimize change.
Case study:
- Mode of transportation
- Representation of a data
- Behaviour of an actor
- Trading algorithm
Entities/Objects involved
- There is a strategy interface encapsulating the behaviour
- There is a context which wraps the strategy and is responsible for getting the data to apply the strategy on.
- There are implementation of the strategy interface.
Motivating problem
Suppose you have a set of datapoints, and you want to visualize them. You can either visualze them as a frequency graph, or as a time chart.
interface VisualizeStrategy {
void visualize(List<DataPoint> dps)
}
class FrequencyGraph() extends visualizeStrategy { ... }
class TimeChart() extends visualizeStrategy { ... }
class ReportContext(VisualizeStrategy visualizeStrategy) {
public void generateReport(List<DataPoint> dps) {
visualizeStrategy.visualize(dps);
}
}
Complications
Here we noticed that we assume all DataPoint
can be visualized via frequency graph and time chart. What if DataPoint
is an abstract class? And there are actually two possible types of data: one with time series and one without?
abstract class DataPoint {}
class TimeSeriesData extends DataPoint {}
class OneDimensionDataPoint extends DataPoint {}
Obviously in the TimeChart
implementation we somehow have to ensure that we only get data that we can handle. For example, this could be done by doing a downcast
public void Visualize(List<DataPoint> dps) {
Stream<TimeSeriesData> supportedData = dps.stream().map(x -> {
if (!(x instanceof TimeSeriesData)) {
throw new IllegalArgumentException("Unsupported data"); // Awkward
} else {
return (TimeSeriesData) x;
}
});
// do things
}
How do we get the compiler to help us prevent the following case?
public void BadReportMain() {
ReportContext r = new ReportContext(new TimeChart);
r.generateReport(new OneDimensionDataPoint()); // This causes an exception
}
This is were self referential generics can help.
Self referential generics
Let’s look at some real life examples of this
public abstract class Enum<E extends Enum<E>> {...}
But unfortunately enum is some special snowflake introduced in Java 1.5 in 2004 and if you try to create a class that extends it you’ll get an error saying Class cannot directly extends Enum Class
Anyway, the benefit of defining these self referential generics is the ability to refer to the subtype in the parent type. Think of it as attaching an extra piece of information.
abstract class DataPoint<D extends DataPoint<D>> {}
class TimeSeriesData extends DataPoint<TimeSeriesData> {}
class OneDimensionData extends DataPoint<OneDimensionData> {}
interface Visualizer<SupportedData extends DataPoint> {
void visualize(List<SupportedData> dps);
}
class TimeChart implements Visualizer<TimeSeriesData> {
public void visualize(List<TimeSeriesData> supportedData) {
// Note that there is no need to do any casting
}
}
Conclusion
Self referential generics is very confusing.