 |
Properties are Design Features
Posted by cayhorstmann on January 18, 2007 at 11:42 AM | Comments (12)
Mapping Design Intent to Code
Chapter 3 in
Fowler's UML
Distilled discusses class diagrams. His first design
concept: properties. Fowler describes the role of properties in
OO design and how they are mapped to language features in Java and C#.
In Java, a public read-write property can be
mapped to a getter and setter pair. The design concept
/** the name of this widget */
public property String name;
gets mapped into
/**
Gets the name of this widget.
@return the name of this widget
*/
public String getName()
{
return . . .;
}
/**
Sets the name of this widget.
@param name the new name of this widget
*/
public void setName(String name)
{
. . .;
}
What's wrong with it?
- Not very DRY
- Humans and tools need to rediscover the designer's intent
Making the mapping from design to code clear and traceable should be
the job of the programming language.
After all, why do we program in Java? You can implement classes in
C—just use struct, write the virtual method tables with
macros, and use some discipline.
Or, to use a more recent analogy, consider the enhanced for
loop. Why do we prefer
for (String w : words)
to
for (int i = 0; i < words.size(); i++)
{
String w = words.get(i);
. . .
}
You could argue that the enhanced for loop is a deplorable
sign of language bloat and that it is properly the job of the IDE to write
the explicit loop.
Or you could argue—as the designers of this feature
have—that the enhanced for loop reduces the cognitive load
on the code reader because it is a more faithful translation of the
programmer's intent, namely to loop through all elements.
I think the IDE argument is completely bogus. I don't care about the
keystrokes. I know perfectly well how to get my IDE to type stuff. I care
about reading the code later. When I read the explicit loop, I look
at the indexes, the < sign, the ++. When they are all
just so, I say to myself “aha—I rediscovered the intent; we
really want to loop through all elements”.
As you can tell, I buy into the “intent” argument. Going
back to properties, when I design a class, I think of properties, not
getters and setters. I want that intent reflected in the code. I don't
want to piece together a dozen lines of getter/setter code, checking that
all the names and parameters are just so.
Designing With Properties
The most common argument that I hear against properties is “they
are bad design—just like public fields”.
There is an essential difference. Once you expose a public field, you
can never change the implementation. But with a property, you can start
out with a trivial implementaton and refine it later, e.g. add
error checking in the setter, or switch from field access to a computed
result.
The usual trite example: Start with a trivial implementation.
/** the name of this widget */
public property String name; // compiler auto-generates trivial getter/setter
Later, refine it to
/** the name of this widget */
public property String name
get { return fname + " " + lname; }
set { String[] n = value.split(" "); fname = n[0]; lname = n[1]; }
(Yes, I know the setter needs better error checking. That will be the
next refinement)
(Yes, I know that the number of keystrokes isn't all that different
from using a regular old getter/setter. That's unimportant. What s
significant is that the programmer intent is captured by grouping the
implementation features together.)
Another
commonly voiced argument is: “You should not expose all internal
state through getters and setters”.
To which I say: “Amen”.
When designing a property, I ask myself whether this is indeed an
intrinsic feature of the class that will always be there. Does it make
sense to allow public read access? Public write access? The fewer
properties, the better.
Some people argue that we shouldn't add property support to Java
because clueless programmers would abuse it. But they already abuse it
today, aided by the getter/setter auto-generation in the IDE. Is Eclipse a
tool of the devil because it makes writing bad code so easy?
Design is hard. The class designer has the arduous task of producing a
public interface that is rich enough to make the class usable and frugal
enough to make it withstand change. I don't know how to simplify that
task. All I ask for is that—once a capable design has been
completed—the design intent is right there in the Java code, rather
than making me recover it from a rubble of code.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
I think the biggest argument for properties is the ability to refactor intrinsicly. Encapsulating behavior on a 'property' today, involves major changes to all external users of the code: "bean.foo = 4;" to "bean.setFoo(4);", or "int f = bean.foo;" to "int f = bean.getFoo()". With property syntax, you wouldn't need to refactor all external users to encapsulate/change the inner behavior.
Posted by: jhook on January 18, 2007 at 12:00 PM
-
I think the IDE argument is completely bogus. I don't care about the keystrokes. I know perfectly well how to get my IDE to type stuff. I care about reading the code later.
I agree that the IDE argument is bogus, but at the same time there's little to stop the IDE from presenting the code better. When I first learned of IDEA's Live Templates, I thought it would be this; IDE-level macros, the expanded form of which you do not see unless you explicitly request it.
Sadly, that wasn't the case.
Posted by: ricky_clarkson on January 18, 2007 at 04:17 PM
-
How would a public getter and private setter look?
Posted by: dwalend on January 18, 2007 at 05:46 PM
-
dwalend: That is a great question--it shows the value of taking a design perspective. From a design perspective, the focus is on the property, not the getter/setter inplementation. The client of the class perceives a public read-only property, and that is what should be declared in the code. What about the "private setter"? It can only be invoked from other methods of the same class. Can those methods simply access the private field(s) that store the property value? If so, there is no need for the "private setter". If there is a need, implement it as a private helper method that is not part of the property.
Posted by: cayhorstmann on January 18, 2007 at 06:03 PM
-
I'll bite. How would a public read-only property look?
Posted by: dwalend on January 18, 2007 at 06:25 PM
-
I hesitate to pontificate about syntax. I'd rather not have those arrows in the back.If the C# style get/set notation is used, one plausible syntax for a read-only property would be
/** the name of this widget */
public property String name
get { return fname + " " + lname; }
// no setter
Posted by: cayhorstmann on January 18, 2007 at 09:04 PM
-
Thanks, Cay, very well put. And nice diagram! :) David
Posted by: davidvc on January 18, 2007 at 09:16 PM
-
Cay, excellent explanation!
I agree with your post 100%, I wouldn't have explained it better.
Thanks!
- Xavi
Posted by: xmirog on January 19, 2007 at 01:11 AM
-
“You should not expose all internal state through getters and setters”.
This is a valid point, but I agree that is not relevant to the discussion, as - good or bad might it be - we _have_ to deal with getters and setters because of JavaBean/POJO related technologies.
"I think the IDE argument is completely bogus. ... I care about reading the code later."
ricky_clarkson already said it, I repeat: why this can't be done with code-folding and presentation by an IDE? For instance NetBeans by default hides all the plumbing code behind a Matisse Swing Panel or a Servlet or whatever. It could as well hide a "default" getter/setter pair for a property.
If we need customized getters/setters, let me point out that the listings comparison you made is not fair, since you used different eol styles and comments could be removed.
private String name;
public String getName()
{
return fname + " " + lname;
}
public void setName(String name)
{
String[] n = value.split(" "); fname = n[0]; lname = n[1];
}
versus
public property String name
get
{
return fname + " " + lname;
}
set
{
String[] n = value.split(" "); fname = n[0]; lname = n[1];
}
Really, I don't see a lot of a difference! Basically javadocs comments makes the difference with the regular getter/setter approach, but they are pretty useless as they can be generated by the javadoc tool or the IDE (eventually a compile-time annotation could be useful). On the contrary, if there's something more to be put in the javadoc, you should add it both to the regular setter and to the new syntax.
Posted by: fabriziogiudici on January 19, 2007 at 01:56 AM
-
As much as i want to see property literals (for reference purposes), a potential issue is that code changes from foo.getBar() to foo.bar.get(), which makes the language seem quite different... but actually maybe its better.
So I would like to see this in the language. I think properties should be inherently supported in the language as first class citizens, sooner rather than later.
Also because Java cannot afford to be too conversative to keep us current java programmers happy. We need to be ready to welcome hordes of C# programmers coming over to our side ;) and attract new programmers, by also offering the features that competing languages tout, notably C#.
Posted by: evanx on January 19, 2007 at 06:22 AM
-
I agree with Cay Horstmann. Adding new features to Java is, or should be, all about increasing readability.
There are some problems with designing using properties, for example:
What looks like a simple assignment (a = b) can have unforseen consequences if a is a property and the code behind it does more than just set a simple value.
It can be difficult to judge when to use a property and when not to. If a property such as price is indirect, i.e. requires complex or time consuming computing when getting the value, then it might be better to have a seperate calculatePrice() method. But where to draw the line? And what about when the price starts out as a simple property reflecting a private field and then evolves into something computed on the fly from a broad variety of factors?
But properties are already a part of Java today. And these problems are already present. When calling getXXX(...) we assume that nothing more than a fairly simple retrieving of a value takes place. There's a convention in place that tells us that calling the getXXX-method just reads the value of XXX.
The discussion, therefore, should not be about wheter or not properties should be a part of Java, but instead wheter properties should be first-class citizens of the language or continue to be based only on conventions, as they are now.
A cheap way to elevate properties from the status of convention and into something more solid could be to include the following two classes in java.lang or some such package:
public class ReadOnlyProperty {
protected T value;
public ReadOnlyProperty() {
super();
}
public ReadOnlyProperty(T initialValue) {
this.value = initialValue;
}
public T get() {
return value;
}
public boolean equals(Object o) {
return value.equals(o);
}
public int hashCode() {
return value.hashCode();
}
}
public class Property extends ReadOnlyProperty {
public Property() {
super();
}
public Property(T initialValue) {
super(initialValue);
}
public void set(T value) {
this.value = value;
}
}
Then property-based tools, such as GUI-designers, could be written to recognise these classes and you could write code as follows:
public class Example1 {
final ReadOnlyProperty name = new ReadOnlyProperty();
public static void main(String[] args) {
new Example1().runExample();
}
private void runExample() {
String n = name.get();
name.set("illegal. Compile error!");
}
}
public class Example2 {
final ReadOnlyProperty p1 = new ReadOnlyProperty("test1");
final ReadOnlyProperty p2 = new ReadOnlyProperty("test2");
public static void main(String[] args) {
new Example2().runExample();
}
private void runExample() {
if (p1.equals(p2)) {
System.out.println("p1 equals p2");
}
}
}
public class Example3 {
final Property name = new Property();
public static void main(String[] args) {
new Example3().runExample();
}
private void runExample() {
System.out.println(name.get());
name.set("John Smith");
}
}
public class Example4 {
final Property name = new Property() {
private String fname;
private String lname;
@Override
public String get() {
return fname + " " + lname;
}
@Override
public void set(String value) {
String[] n = value.split(" ");
fname = n[0]; lname = n[1];
}
};
public static void main(String[] args) {
new Example4().runExample();
}
private void runExample() {
System.out.println(name.get());
name.set("John Smith");
}
}
Posted by: nicba on January 20, 2007 at 10:31 AM
-
Ahhh, damn! The commenting software ripped out all the generics from my code. It was supposed to go something like this:
public class Property<T> extends ReadOnlyProperty<T> {
...
}
...
final Property<String> name = new Property<String>();
And so on in all the examples. I hope you got the gist of it anyway.
Posted by: nicba on January 20, 2007 at 10:51 AM
|