- give it a name
- write all properties (fields)
- generate all public getters and setters - finished!
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:
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!/** * 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; } }
So how can we do better? What about this variant:
Much shorter, well-documented, much more encapsulation, less runtime-risks: an immutable object!/** * 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; } }
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 eitherprotected
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:
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.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; } }
But: dependency injection tools will not be satisfied, because setters are missing!
So here is the write-layer:
Now also dependency injection should be happy.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; } }
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:
Kommentar veröffentlichen