Forker
Overview
Forker<F> is the strategy object that knows how to build a forked instance of a specific entity type F.
The interface defines three methods, one per ForkMode:
public interface Forker<F> {
default F toCopy() { throw new UnsupportedOperationException(...); }
default F toNext() { throw new UnsupportedOperationException(...); }
default F toPrevious() { throw new UnsupportedOperationException(...); }
}
Every method has a default implementation that throws UnsupportedOperationException — implementations override only the modes they support.
A common pattern is to support ToNext (period-boundary carry-forward) and ToCopy, but not ToPrevious.
Implementing a Forker
A Forker is typically a small class that holds a reference to the original and produces new instances:
public class NinjaForker implements Forker<Ninja> {
private final Ninja original;
public NinjaForker(Ninja original) { this.original = original; }
@Override
public Ninja toNext() {
var n = new Ninja();
n.setUuid(UUID.randomUUID());
n.setSeason(original.getSeason() + 1);
n.setForkedFrom(original.toForkSources(ForkMode.ToNext));
n.setDojo(ForkRefAware.ofNullable(original.getDojo(), ForkMode.ToNext));
n.setRank(original.getRank());
return n;
}
}
Three things to notice:
-
Lineage is recorded via
original.toForkSources(ForkMode.ToNext)— you don’t constructForkSourcesby hand. -
References to other forkable entities (here
dojo) go throughForkRefAware.ofNullable(…), which produces a minimal shell with just the UUID and lineage. This avoids deep-copying entire object graphs, and lets the consumer decide later whether to materialise the full child. -
Plain data fields (here
rank) are copied directly.
The dispatch from Forkable.fork(ForkMode) to the right Forker method is handled by the library:
@Override
public Forker<Ninja> fork() {
return new NinjaForker(this);
}
// caller:
var next = original.fork(ForkMode.ToNext);