Blog-Archiv

Montag, 1. September 2014

Unbeloved Constructors, Beloved Anemic Objects

As one can see in many project teams, the normal way to write a Java class is this:
  • give it a name
  • write all properties (fields)
  • generate all public getters and setters - finished!
This is done on a large scale, most projects are full of such classes. They are justified by the Java Bean concept, and a lot of tools and libraries that rely on java.lang.reflect even need them, especially dependency-injection tools (DI). Rarely they get JavaDoc, they are said to be "self-documenting" :-)

Nevertheless such classes lack encapsulation and logic. They are called anemic objects (which is the name of an anti-pattern).

The following thoughts are around using constructors instead of the ubiquitous setters that endanger integrity at any time and from everywhere. Moreover I try to split such classes into a readonly- and a read/write-layer.



Here is a typical anemic class:
/**
 * The inevitable ubiquitous undocumented anemic object, generated by some IDE.
 * Any property can be modified from outside. This class encapsulates nothing.
 * Public setters, once released in a big project, might be called by reflection
 * and are not disposable anymore without risk.
 */
public class Contact
{
    private String personName;
    private boolean inCountry;
    private String phoneNumber;
    private String realm;
    private int ranking;
    
    public String getPersonName() {
        return personName;
    }
    public void setPersonName(String personName) {
        this.personName = personName;
    }
    public boolean isInCountry() {
        return inCountry;
    }
    public void setInCountry(boolean inCountry) {
        this.inCountry = inCountry;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    public String getRealm() {
        return realm;
    }
    public void setRealm(String realm) {
        this.realm = realm;
    }
    public int getRanking() {
        return ranking;
    }
    public void setRanking(int ranking) {
        this.ranking = ranking;
    }
}
This is also a problem when programming quickly via auto-complete in an IDE like Eclipse. The mass of names that show up when searching for a method is a real show-stopper. We'd liked only the really needed methods to be public!

So how can we do better? What about this variant:
/**
 * An immutable value-object (data-transfer-object).
 * No property can be modified from outside. This class encapsulates read-only data.
 * Quick to read, easy to understand, safe against abuse, well-documented.
 */
public class Contact
{
    /** The person this contact aims at. */
    public final String personName;
    /** True when the person is easily reachable in country. */
    public final boolean inCountry;
    /** The phone connection to the person. */
    public final String phoneNumber;
    /** The area of expertise the person can be asked about. */
    public final String realm;
    /** High when the person is a real expert in realm, low otherwise. */
    public final int ranking;
    
    public Contact(
            String personName,
            boolean inCountry,
            String phoneNumber,
            String realm,
            int ranking)
    {
        this.personName = personName;
        this.inCountry = inCountry;
        this.phoneNumber = phoneNumber;
        this.realm = realm;
        this.ranking = ranking;
    } 
}
Much shorter, well-documented, much more encapsulation, less runtime-risks: an immutable object!
Dependency injection tools might not like that variant, but human beings do.

Mind that normally it is a very bad practice to create public fields (make it private and provide a getter). But in this case the final access modifier ensures that the field is not changed any more after construction.
So, isn't this a reason to love constructors?

But lets go a little further and try to find a way to define an immutable value-object, and, as a derivation of this, a class that adds write-access.


The two-layer object and its constructors

For a field to be writable from a sub-class it needs to be either protected or package-visible. I decided for the package-visible variant here, because I wanted to force any class that provides write-access to be in the same package.

Here is the readonly-layer:
package sample;

/**
 * An immutable value-object (data-transfer-object).
 * No property can be modified from outside. This class encapsulates read-only data.
 * Package-visible fields prevent access from sub-classes outside this package.
 */
public class ContactData
{
    String personName;
    boolean inCountry;
    String phoneNumber;
    String realm;
    int ranking;
    
    /** Read-only constructor. */
    public ContactData(
            String personName,
            boolean inCountry,
            String phoneNumber,
            String realm,
            int ranking)
    {
        this.personName = personName;
        this.inCountry = inCountry;
        this.phoneNumber = phoneNumber;
        this.realm = realm;
        this.ranking = ranking;
    }

    /** Facilitate a write-access sub-class in same package. */
    ContactData()    {
    }
    
    /** @return the person this contact aims at. */
    public String getPersonName() {
        return personName;
    }
    /** @return true when the person is easily reachable in country. */
    public boolean isInCountry() {
        return inCountry;
    }
    /** @return the phone connection to the person. */
    public String getPhoneNumber() {
        return phoneNumber;
    }
    /** @return the area of expertise the person can be asked about. */
    public String getRealm() {
        return realm;
    }
    /** @return high when the person is a real expert in realm, low otherwise. */
    public int getRanking() {
        return ranking;
    }
}
As we can see, this is immutable, no setters are present. Fields are not final, but well encapsulated. In the public constructor you could assert certain values to be not null.
But: dependency injection tools will not be satisfied, because setters are missing!

So here is the write-layer:
package sample;

/**
 * A mutable data-transfer-object that provides write-access to all properties.
 * In fact this does not encapsulate anything, but it reuses its super-class.
 * So you can decide later whether a write-layer makes sense.
 */
public class ContactModel extends ContactData
{
    /** For new objects getting values from UI. */
    public ContactModel()    {
    }
    
    /** For objects getting values from persistence. */
    public ContactModel(ContactData data)    {
        super(
            data.getPersonName(),
            data.isInCountry(),
            data.getPhoneNumber(),
            data.getRealm(),
            data.getRanking());
    }

    /** Sets the person this contact aims at. */
    public void setPersonName(String personName) {
        this.personName = personName;
    }
    /** Sets whether the person is easily reachable in country. */
    public void setInCountry(boolean inCountry) {
        this.inCountry = inCountry;
    }
    /** Sets the phone connection to the person. */
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    /** Sets the area of expertise the person can be asked about. */
    public void setRealm(String realm) {
        this.realm = realm;
    }
    /** Sets high when the person is a real expert in realm, low otherwise. */
    public void setRanking(int ranking) {
        this.ranking = ranking;
    }
}
Now also dependency injection should be happy.
This class does not encapsulate anything, but for a data-transfer-object this is nothing peculiar.
The asset of this design is that we have two layers we can use separately. We could construct a read-only object and use it as data-transfer-object. We could then construct a read/writable object from that template when we need it for an editor.

I'm not saying that this is a solution for Command and Query Responsibility Segregation, but isn't it the same idea?

Keine Kommentare: