Sonntag, 17. April 2016

FXObservable, creating JavaFX Observables without the boilerplate

When working with JavaFX by default the following pattern is recommended
import javafx.beans.property.*;
public class Model {
    private StringProperty theString;

    public String getTheString() {
         return theStringProperty().get();

    }
    public void setTheString(String theString) {
        theStringProperty().set(theString); 

    }
    public StringProperty theStringProperty() {
        if (theString == null) {
            theString = new SimpleStringProperty(this, "theString") 
        }
        return theString;
    }
}
This pattern enhances the default Java bean pattern (having getter and setter methods) with a third method to retrieve a bindable property. The heart of this pattern is in having a member property, a property accessor method and getter and setter methods using the property to retrieve and set the value. The pattern exists in variants with eager initialization and lazy creation of the property member. The above example uses the lazy initialization aproach.
Dirk Lemmermann had some issues with the widespread usage of this pattern at a customer, since the JavaFX property objects use more memory than the simple objects they wrap. He created a pattern called Shadow Fields. The essence of that pattern is to only create a JavaFX property when really needed. To achieve this, he introduces another member for the simple type and does not create a property as long as the property method is not called. Using his pattern, the above code would become
import javafx.beans.property.*;
public class Model {
    private String _theString; 
    private StringProperty theString;

    public String getTheString() {
         return theString == null ? _theString : theString.get();
    }
    public void setTheString(String theString) {
        if (this.theString == null) _theString = theString;
        else this.theString.set(theString);
    }    public StringProperty theStringProperty() {
        if (theString == null) {
            theString = new SimpleStringProperty(this, "theString", _theString); 
        }
        return theString;
    }
}
While the default pattern is already a mouthful, the Shadow Fields pattern introduces some more boilerplate code. That’s why Carl Dea started a blog series introducing a new pattern to overcome some of this boilerplate. The base of his approach is to use an interface with default methods. But there is still a lot of boilerplate involved, even with Carl’s approach.
I am a long time fan of Griffon. When writing a Griffon application in Groovy, the above model would look like this
import griffon.transform.FXObservable 
class Model {
    @FXObservable 
    String theString
}
That’s right! There are no getters, setters or methods to retrieve the property. A Groovy AST transformation generates all the boilerplate during compilation of the code.
In my day job I develop a Swing based application written using a custom framework. Currently I am planning to replace parts of this custom framework with Griffon and gradually switch to JavaFX. But since we currently are not planning to use Groovy in production code (we are using Spock for writing our tests), I was already thinking about implementing a FXObservable handler for Lombok, when Andres Almiray tweeted he had the same idea. That’s when my journey of implementing my first Lombok handler began.
After reading Carl’s and Dirk’s posts I changed the existing nearly complete handler to implement the Shadow Fields pattern. That’s right. Using Lombok, it is now possible to implement the Shadow Fields pattern in Java using the following code:
import griffon.transform.FXObservable;
public class Model {
    @FXObservable 
    private String theString;
}
Yes. the only difference to the Groovy version is in two additional words (public and private) and two semicolons.
During the discussion Carl came up with the idea of using only a single object field instead of two dedicated fields:
import javafx.beans.property.*;
public class Model {

    private Object theString; 

    public final String getTheString() {
        return theString instanceof StringProperty ?
                ((StringProperty)theString).get() :
                (String) theString;
    }
    public final void setTheString(String theString) {
        if (this.theString instanceof StringProperty)
            ((StringProperty)this.theString).set(theString);
        else
            this.theString = theString;
    }
    public final StringProperty theStringProperty() {
        if (!(theString instanceof StringProperty)) {
            theString = new SimpleStringProperty(this, "theString", (String)theString);
        }
        return (StringProperty)theString;
    }
}
This last change is not yet implemented in the Lombok handler, but will most probably be. It is only to be discussed, if the FXObservable annotation will be enhanced with an option to configure the behaviour of the generated code (whether to use the original pattern, Dirk’s Shadow Fields pattern, Carl’s Property Accessor Interface, or the enhanced Single Object variant of the Shadow Fields pattern).

Keine Kommentare: