vendredi 27 juillet 2012

Smart pojo

Dans cet article je vous propose de la description d'un pojo respectant certaines bonnes pratiques de développement que sont l'immuabilité et une API sémantique et facile à utiliser et à relire.


Tout d'abord partons d'un pojo classique et essayons de le rendre immuable :

public class MyClass {
    private String myStringField;
    private Boolean state;
    private Date myDate;

    public String getMyStringField() {
        return myStringField;
    }

    public void setMyStringField( String myStringField ) {
        this.myStringField = myStringField;
    }

    public Boolean getState() {
        return state;
    }

    public void setState( Boolean state ) {
        this.state = state;
    }

    public Date getMyDate() {
        return myDate;
    }

    public void setMyDate( Date myDate ) {
        this.myDate = myDate;
    }
}
La première étape est de déclarer tous les attributs avec le mot clé final et de supprimer tous les setters. Il faut aussi forcement déclarer un constructeur pour initialiser chacun des attributs.

public class MyClass {
    private final String myStringField;
    private final Boolean state;
    private final Date myDate;

    public MyClass( final String myStringField, final Boolean state, final Date myDate ) {
        this.myStringField = myStringField;
        this.state = state;
        this.myDate = myDate;
    }

    public String getMyStringField() {
        return myStringField;
    }

    public Boolean getState() {
        return state;
    }

    public Date getMyDate() {
        return myDate;
    }
}

La déclaration final pour les paramètres du constructeur ne sont là que pour indiquer à l'utilisateur de la classe que celle-ci ne modifiera pas la valeur de la référence qu'il envoie.
Donc maintenant il nous est impossible de modifier la valeur des attributs privés...
En fait pas tout à fait. Le champ de type Date en particulier pose problème du fait que le type Date soit muable. Il est très possible via la méthode getMyDate() de récupérer la référence de notre attribut et de faire par exemple :

long uneAutreDate = 123456789L;
new MyClass( "string", true, new Date() ).getMyDate().setTime( uneAutreDate );
Nous pouvons résoudre ce problème en deux temps grâce au defensive copies pattern.
Premièrement, en renvoyant le clone de la référence de myDate et non la référence elle-même.
Deuxièmement en créant une nouvelle référence à l'initialisation de la classe et ne pas se servir de celle passée en paramètre.

public class MyClass {
    private final String myStringField;
    private final Boolean state;
    private final Date myDate;

    public MyClass( final String myStringField, final Boolean state, final Date myDate ) {
        this.myStringField = myStringField;
        this.state = state;
        this.myDate = defensiveCopyForDateParameter( myDate );
    }

    public String getMyStringField() {
        return myStringField;
    }

    public Boolean getState() {
        return state;
    }

    public Date getMyDate() {
        return ( myDate != null ) ? ( Date ) myDate.clone() : null;
    }

    private Date defensiveCopyForDateParameter( Date date ) {
        return ( date != null ) ? new Date( date.getTime() ) : null;
    }
}
Nous avons à disposition une classe MyClass immuable.

Essayons maintenant de designer une API d'initialisation sémantique. En effet quand je veux initialiser mon pojo je dois faire :

MyClass theClass = new Class( "un string", false, new Date() );
Ainsi il faut que je me souvienne de l'ordre des paramètres à utiliser pour initialiser ma classe.
De plus, si je relis le code, il n'est pas aisé de comprendre à quoi fait allusion chaque paramètre. Que décrit "un string" ? false ? Ou pire si la date n'est pas nécessaire et que je mette null ? Je suis obligé d'aller voir dans le code de la classe pour le savoir.

L'idée est de se servir du pattern ObjectBuilder souvent utilisé dans les tests unitaires :

public class MyClass {
    private final String myStringField;
    private final Boolean state;
    private final Date myDate;

    private MyClass( final String myStringField, final Boolean state, final Date myDate ) {
        this.myStringField = myStringField;
        this.state = state;
        this.myDate = defensiveCopyForDateParameter( myDate );
    }

    public String getMyStringField() {
        return myStringField;
    }

    public Boolean getState() {
        return state;
    }

    public Date getMyDate() {
        return ( myDate != null ) ? ( Date ) myDate.clone() : null;
    }

    private Date defensiveCopyForDateParameter( Date date ) {
        return ( date != null ) ? new Date( date.getTime() ) : null;
    }

    public static class Builder {
        private String myStringField;
        private Boolean state;
        private Date myDate;

        public MyClass toMyClass() {
            return new MyClass( myStringField, state, myDate );
        }
        
        public Builder withMyStringField( String myStringField ) {
            this.myStringField = myStringField;
            return this;
        }

        public Builder withState( Boolean state ) {
            this.state = state;
            return this;
        }

        public Builder withMyDate( Date myDate ) {
            this.myDate = myDate;
            return this;
        }
    }
}
Nous avons maintenant à disposition une API sémantique nous permettant d'initialiser un pojo immuable :

MyClass theClass = new MyClass.Builder()
                           .withMyStringField( "the string" )
                           .withState( true )
                           .withMyDate( new Date() )
                           .toMyClass();
Cool non ? ;)

une description du ObjectBuilder pattern
defensive copies pattern

Aucun commentaire:

Enregistrer un commentaire