I responded to this question about designing around interdependant methods with the strategy pattern.
Afterwards I started thinking about the functional additions to java 8 and wondered if I was being an old foggy stuck in the past and if maybe there was a better way with all these fancy new tricks.
So I started hammering on the idea with single method interfaces. Not as elegant as lambdas I know but it's what I found myself doing.
Here's the gist of the original question:
... only depend on the three fundamental methods int Length(), Set DescentSet() and int[] Normalform(). Normally this means that I would use the method template pattern, i.e. I would make these three methods abstract and let the concrete implementations deal with it. But: Those methods are interdependent. I really only need any one of them to define the other two. ...
Problem is performance concerns demand we be able to pick which one is NOT dependant on the others.
The strategy pattern works well here but on the off chance even more flexibility is desired I tried this:
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
//design-pattern-for-interdependent-abstract-methods
//https://softwareengineering.stackexchange.com/q/333608/131624
public class EntryPoint {
public static void main(String[] args) {
long startTime = System.nanoTime();
System.out.println(".-=== Interdependent test start ===-.");
int i = 1;
List<Interdependent> ids = Arrays.asList(
// int i, IntInLen r, IntInSet s, IntInArray a
new IntPackage(i, new Leng(), new DSetFromLeng(), new NormFromLeng() ),
new IntPackage(i, new LengFromDSet(), new DSet(), new NormFromDSet() ),
new IntPackage(i, new LengFromNorm(), new DSetFromNorm(), new Norm() )
);
for (Interdependent id : ids) {
System.out.println(id.length() + " : " +
id.descentSet().iterator().next() + " : " +
id.normalform()[0]);
assertEquals(i, id.length());
assertEquals(i, (int)id.descentSet().iterator().next());
assertEquals(i, id.normalform()[0]);
}
System.out.println("'-=== Interdependent test end ===-'");
long estimatedTime = System.nanoTime() - startTime;
double time = (double)estimatedTime / 1000000000.0;
System.out.println("time = " + time);
}
}
interface Interdependent {
int length();
Set<Integer> descentSet();
int[] normalform();
}
interface IntInLen {
int length(Interdependent id, int i);
}
interface IntInSet {
Set<Integer> descentSet(Interdependent id, int i);
}
interface IntInArray {
int[] normalform(Interdependent id, int i);
}
class IntPackage implements Interdependent {
private int i;
IntInLen r;
IntInSet s;
IntInArray a;
public IntPackage(int i, IntInLen r, IntInSet s, IntInArray a) {
this.i = i;
this.r = r;
this.s = s;
this.a = a;
}
@Override
public int length() {
return r.length(this, i);
}
@Override
public Set<Integer> descentSet() {
return s.descentSet(this, i);
}
@Override
public int[] normalform() {
return a.normalform(this, i);
}
}
// .-=== IntInLen ===-. //
class Leng implements IntInLen {
@Override
public int length(Interdependent ip, int i) {
return i;
}
}
class LengFromDSet implements IntInLen {
@Override
public int length(Interdependent id, int i) {
return id.descentSet().iterator().next();
}
}
class LengFromNorm implements IntInLen {
@Override
public int length(Interdependent id, int i) {
return id.normalform()[0];
}
}
// '-=== IntInLen ===-' //
// .-=== IntInSet ===-. //
class DSetFromLeng implements IntInSet {
@Override
public Set<Integer> descentSet(Interdependent id, int i) {
Set<Integer> s = new HashSet<>();
s.add(id.length());
return s;
}
}
class DSet implements IntInSet {
@Override
public Set<Integer> descentSet(Interdependent id, int i) {
Set<Integer> s = new HashSet<>();
s.add(i);
return s;
}
}
class DSetFromNorm implements IntInSet {
@Override
public Set<Integer> descentSet(Interdependent id, int i) {
Set<Integer> s = new HashSet<>();
s.add(id.normalform()[0]);
return s;
}
}
// '-=== IntInSet ===-' //
// .-=== IntInArray ===-. //
class NormFromLeng implements IntInArray {
@Override
public int[] normalform(Interdependent id, int i) {
int[] result = new int[1];
result[0] = id.length();
return result;
}
}
class NormFromDSet implements IntInArray {
@Override
public int[] normalform(Interdependent id, int i) {
int[] result = new int[1];
result[0] = id.descentSet().iterator().next();
return result;
}
}
class Norm implements IntInArray {
@Override
public int[] normalform(Interdependent id, int i) {
int[] result = new int[1];
result[0] = i;
return result;
}
}
// '-=== IntInArray ===-' //
Outputs:
.-=== Interdependent test start ===-.
1 : 1 : 1
1 : 1 : 1
1 : 1 : 1
'-=== Interdependent test end ===-'
time = 0.00656082
This kinda thing have a name?
This puts a lot of flexibility in our hands. We can extend any number of implementations of the methods completely independently.
However, this also gives us the ability to shoot ourselves in the foot:
new IntPackage(i, new LengFromNorm(), new DSetFromLeng(), new NormFromLeng() )
That will reliably produce a stack overflow.
I know this is more flexibility that was actually asked for but is there some twist on this (functional or otherwise) that I'm simply not seeing that could do the strategy pattern one better?
This was fun but still feels overdesigned for these requirements.