Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent Interface (Java) for object with getters and setters

I read and enjoyed the article http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course/ from Lukas Eder and I would like to create a Fluent Interface for a class.

The class has four functions ("words" fill1 to fill4) that allow to set object attributes and four function ("words" get1 to get4) that get those attributes, but only if the required attributes have been set:

First I have to fill in the basic settings (fill1). After that I am either able to get some of those settings (get1 to get3) which are Strings. Or I am able to fill in some more information (fill2 to fill4). But only after every fill2 to fill4 has been invoked at least once, the final get4 can be invoked. How do I do this?

The first graph (states are the black dots) shows what I want to do, but as you can see the ? marks the part which is not clear, because if left as it is in the first graph, it would allow get4 to be called even if only one of fill2 to fill4 has been called.

The second graph would force that each fill2 to fill4 has been called but inforces the order and restricts that if I want to change e.g. fill3, I have to reset fill2 and fill4 as well.

The last graph would do what I want, but it has 13 states! Now if I imagine I would just add one more attribute to the group of fill2 to fill4, the number of states would explode even more.

enter image description here

Edit: Also, after thinking about it some more, I noticed that the way I'd do this (see below), would not even be able to implement the last graph, because after fill2 is invoked, we might be in different states - depending on what happened before.

What can / should I do?

Edit: I implement my fluent interfaces a little like a facade (if I got the design pattern right). What I mean: I leave the actual class nearly untouched - returning this (as in method chaining), however having the respective state interfaces as return values in the method signatures. The states are represented by nested interfaces. Example:

public class MyClass implements MyInterface.Empty, MyInterface.Full {

    String stuff;

    private MyClass(){};

    public static MyInterface.Empty manufactureNewInstance(){
        return new MyClass();
    }

    public MyInterface.Full fillStuff(String stuff){
        this.stuff = stuff;
        return this;
    }

    public String getStuff(){
        return stuff;
    }

}

public interface MyInterface {

    public interface Empty {
        public MyInterface.Full fillStuff();
    }

    public interface Full {
        public String getStuff();
    }

}

public class MyMain {

    pulic static void main(String[] args) {

        // For explaination:
        MyClass.Empty myclassEmpty = MyClass.manufactureNewInstance();
        MyClass.Full myclassFull = myclassEmpty.fillStuff("Hello World 1!");
        String result1 = myclassEmpty.getStuff();

        // As fluent chaining:
        String result2 = MyClass.manufactureNewInstance()
            .fillStuff("Hello World 2!")
            .getStuff();

    }

}
like image 751
Make42 Avatar asked Sep 23 '15 13:09

Make42


1 Answers

Fluent interface APIs in Java are somewhat limited by the fact that you need to represent each API state by a different type. This is a consequence of Java types being the only way to communicate a set of API constraints to the Java compiler for validation.

Obviously, this is an unfortunate limitation when creating fluent APIs. To overcome this limitation you either need to:

  1. Implement all the API states manually by, for example, an interface. This can be a feasible solution if your code is unlikely to change, e.g. in a project with rather limited scope that is not supposed to live for too long. A single backing class can then implement all the interfaces that represent each API state. As long as users do not use reflection or type castings, the compiler validates that methods are invoked in a legal order.

  2. Auto-generate the code. This is a more ambitious approach but it can save you a lot of typing and refactoring effort if your API state-combinations "explode" as you say. I wrote a code generation library called Byte Buddy and I know of users using the library for creating interfaces for fluent APIs. (Unfortunately, the two users who I was in contact with on this matter did not open-source their code.) If you rather want to create Java source code instead of Java byte code then Java poet might be an alternative for you which I have also seen for this use case.

  3. Simplify your API to only validate the most common mistakes while checking for less common mistakes by exceptions at runtime. This often is a feasible solution as it makes the API more approachable.

like image 68
Rafael Winterhalter Avatar answered Nov 15 '22 05:11

Rafael Winterhalter