I apologize in advance if this isn’t super helpful, but if somebody on my team wanted to do this I would tell them not to. For starters, “inheritance considered harmful” is not too far from the truth. There are a lot of problems with inheritance based designs and typing “java favor…” into google gives “favor composition over inheritance” as the first result. :)
I am a fan of fluent style APIs in general terms, though I think they can also be abused enormously. If you look at the way the Spring Framework does some fluent interfaces they can become very impenetrable and you can end up with one line statements in Spring Security (see 5.2) that look something like
httpSecurity
.createsANewContext()
.setSomething()
.setOther()
.and()
.alsoCreateAnotherContext()
.withThisSetting("foo")
.etc()
I really don’t like it when they do that.
At any rate, I would really suggest that if the goal is to create a fluent pattern where you take an object and you can perform one or more slightly different operations on it in a fluent manner then the best thing would be to create helper classes that take that object type and act on it in three different ways.
I’ve created a small Java project that does this in a few slightly different ways. The best one in my opinion is the Java 8 functional style where you define Alpha as a Consumer (I’ve created an unnecessary intermediate interface so that if you aren’t using Java 8 you could rework the GreekLetter interface not extend Consumer and instead implement it’s own custom “accept” or “process” method instead of inheriting Consumer.accept())
This ends up looking like this:
Alpha<String> a = new Alpha<>();
Beta<String> b = new Beta<>();
Gamma<String> g = new Gamma<>();
new GreekAlphabet<String>().addLetter(a).addLetter(b).addLetter(g).accept("Composing");
or
new FluentGreekAlphabet<>("Fluent").applyLetter(a).applyLetter(b).applyLetter(g);
or best yet
a.andThen(b).andThen(g).accept("Consumer");
depending on which approach is used.
Here’s an example of one of the letters:
public class Alpha<T> implements GreekLetter<T> {
@Override
public void accept(T t) {
System.out.println("Alpha: " + t.toString());
}
}
and
@FunctionalInterface
public interface GreekLetter<T> extends Consumer<T> {
}
and
public class FluentGreekAlphabet<T> {
private T t;
public FluentGreekAlphabet(T t) {
this.t = t;
}
public FluentGreekAlphabet<T> applyLetter(GreekLetter<T> letter) {
letter.accept(t);
return this;
}
}
and
public class GreekAlphabet<T> implements GreekLetter<T> {
private Set<GreekLetter<T>> letters = new HashSet<GreekLetter<T>>();
public GreekAlphabet<T> addLetter(GreekLetter<T> letter) {
this.letters.add(letter);
return this;
}
@Override
public void accept(T t) {
for(GreekLetter<T> letter : letters) {
letter.accept(t);
}
}
}