Avoiding boilerplate in Java interfaces

361 Views Asked by At

I'm trying to build a key-value store with stored procedures and I've found that enums come in handy for defining things. I'd like a DB to be an enum of Tables, and a Table is associated with an enum of Operations and an enum of Regions. The problem is that enums can't extend abstract classes, so I have to use interfaces. So I have a lot of enums and each one has to implement the same code (define the same fields, write the same constructor to populate those fields, and override the getters and methods that use those fields). Here's an example of such an enum:

interface Operation<VAL, PARAM>
{
    int code();
    Serdes<PARAM> pSerdes();
    VAL apply(VAL val, byte[] pbytes);
}

...

// Job, Param, & Ticket are protobuf classes
public enum JobOp implements Operation<Job, Param>
{
    AddTicket(0, (job, param) -> ...),
    RemoveTicket(1, (job, param) -> ...),
    CompleteTicket(2, (job, param) -> ...);

    private final int code;
    private final Serdes<Param> pSerdes = new ProtoSerdes<>(Param.PARSER);
    private final BiFunction<Job, Param, Job> proc;
    JobOp(int code, BiFunction<Job, Param, Job> proc)
    {
        this.code = code
        this.proc = proc
    }

    @Override
    public int code() { return code; }
    @Override
    public Serdes<Param> pSerdes() { return pSerdes; }
    @Override
    public Job apply(Job val, byte[] pbytes)
    {
        final Param param = pSerdes.fromBytes(pbytes);
        return proc.apply(val, param);
    }
}

I'd like to avoid all the repetitive boilerplate and respect DRY as much as possible, so I've begun using inner classes to represent fields:

interface Operation<VAL, PARAM>
{
    default int code() { return imp().code; }
    default Serdes<PARAM> pSerdes() { return imp().pSerdes; }
    default VAL apply(VAL val, byte[] pbytes) { return imp().apply(val, pbytes); }
    // Reduce the number of fields and getters to implement to one: imp
    Imp<VAL, PARAM> imp();

    class Imp<VAL, PARAM>
    {
        public final int code;
        public final Serdes<PARAM> pSerdes;
        private final BiFunction<V, P, V> proc;

        Imp(int code, Serdes<PARAM> pSerdes, BiFunction<V, P, V> proc)
        {
            this.code = code;
            this.pSerdes = pSerdes;
            this.proc = proc;
        }

        VAL apply(VAL val, byte[] pbytes)
        {
            PARAM param = pSerdes.fromBytes(pbytes);
            return proc.apply(val, param);
        }
    }
}

...

// Job, Param, & Ticket are protobuf classes
public enum JobOp implements Operation<Job, Param>
{
    AddTicket(0, (job, param) -> ...),
    RemoveTicket(1, (job, param) -> ...),
    CompleteTicket(2, (job, param) -> ...);

    private final Serdes<Param> pSerdes = new ProtoSerdes<(Param.PARSER);
    private final Operation.Imp<Job, Param> imp;
    JobOp(int code, BiFunction<Job, Param, Job> proc)
    {
        imp = new Imp(code, pSerdes, proc);
    }

    @Override
    public Imp<Job, Param> imp() { return imp; }
}

This helps cut down on boilerplate, but I'm fairly new to Java and worried this might be an anti-pattern. It also exposes a variable that I don't want to be part of the interface and adds an extra level of function calls that might hurt performance. One solution I thought of for keeping the public interface clean is to keep the fields objects in a static WeakHashMap between keyed on an instance of the interface:

interface Operation<VAL, PARAM>
{
    default int code() { return imp(this).code; }
    default Serdes<PARAM> pSerdes() { return imp(this).pSerdes; }
    default VAL apply(VAL val, byte[] pbytes) { return imp(this).apply(val, pbytes); }
    final Map<Operation, Imp> imps = new WeakHashMap<>();
    @SuppressWarnings("unchecked")
    static <V, P> Imp<V, P> imp(Operation<V, P> op) { return imps.get(op); }
    static <V, P> void bind(Operation<V, P> op, Imp<V, P> imp) { imps.put(op, imp); }

    class Imp<VAL, PARAM>
    {
        public final int code;
        public final Serdes<PARAM> pSerdes;
        private final BiFunction<V, P, V> proc;

        Imp(int code, Serdes<PARAM> pSerdes, BiFunction<V, P, V> proc)
        {
            this.code = code;
            this.pSerdes = pSerdes;
            this.proc = proc;
        }

        VAL apply(VAL val, byte[] pbytes)
        {
            PARAM param = pSerdes.fromBytes(pbytes);
            return proc.apply(val, param);
        }
    }
}

...

// Job, Param, & Ticket are protobuf classes
public enum JobOp implements Operation<Job, Param>
{
    AddTicket(0, (job, param) -> ...),
    RemoveTicket(1, (job, param) -> ...),
    CompleteTicket(2, (job, param) -> ...);

    private final Serdes<Param> pSerdes = new ProtoSerdes<(Param.PARSER);
    JobOp(int code, BiFunction<Job, Param, Job> proc)
    {
        Operation.bind(this, new Imp(code, pSerdes, proc));
    }
}

This idea was inspired by Python's properties, but it has some problems:

1) It doesn't fully get rid of the pollution of the public interface, it just obfuscates it a little.

2) It probably incurs an even greater performance penalty.

3) It implicitly requires the implementer to bind a Fields object in the constructor.

Is there a better way of doing this? Are the above two solutions more deeply problematic than what I've pointed out (performance hits and unclean code)? I'm trying to keep with the Law of Demeter, but this feels like it might cause some coupling issues later on.

EDIT: added 'final' where appropriate

EDIT 2: Added clarification to opening paragraph and cleaned up examples a bit

2

There are 2 best solutions below

1
On

What's wrong with using a parent class for this? Instead of interface Operation, have:

public abstract class Operation<VAL, PARAM> {
    private int code;

    public int getCode() { return code; }
    public abstract void example();
}

... and take it from there.

Unlike interfaces, classes can have state. In the above example, getCode will use the implementation stated but you can override it if you like (you can add final as a keyword to stop that), and the method example MUST be implemented by subclasses. It's similar to just a plain void example(); in an interface (in interfaces, all method declarations are public and abstract by default, in classes you have to add those keywords).

If it can't be a superclass this is not a good idea; the general notion is that interfaces really shouldn't have state. If you insist on having stateful interfaces, you're more or less committed to dirty hacks.

2
On

Are you designing a God class which knows everything and capable of executing anything?
The visitor pattern may divide the tasks into parts and assembly them to be executed.