Tuesday, August 31, 2010

The Advantages of Using Properties Instead of Getters/Setters in Java and Scala

There have been a few concepts I've been pushing for most of my programming career, and one of the top items on that list is Properties. This is a topic I've been meaning to delve into for a while, and have previously, but as this is an incredibly important part of the core of Sgine I would like to take some time to state the case for Properties. This post is all about why Properties are useful and ultimately why Sgine uses them. The next post, Properties - Tutorial, will go into great detail about how to use Properties within the context of Sgine. Properties as a term is bandied about in many different forms, so first a definition is in order.

Definition


In the context of this discussion a Property is the concept of a wrapper class around a value as an alternative to the standard private field, public getter/setter. For example in Java:
public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

person.setName("John Doe");
String name = person.getName();

The property alternative:
public class Person {
    public final name = new Property<String>(null);
}

person.name.set("John Doe");
String name = person.name.get();

A basic Property class in Java might look something like:
public class Property<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

Or in Scala:
class Person {
    private var _name: String = _

    def name = _name

    def name_=(name: String) = _name = name
}

person.name = "John Doe"
val name = person.name

The property alternative:
class Person {
    val name = new Property[String](null)
}

person.name := "John Doe"
val name = person.name()

A basic Property class in Scala might look something like:
class Property[T] {
    private var value: T = _

    def :=(value: T) = this.value = value

    def apply() = value
}

Since this blog is about Sgine, a 3d engine in Scala, I will be showing all further example code only in Scala though much of this still applies directly to Java.

Advantages


This is all well and good, but for all practical appearances it's about the same as classic getter/setter pattern. Why would you want to break convention and use Properties instead?

In the examples above there isn't much practical difference between the two methodologies. Performance-wise they are identical. Properties reduce the amount of code in your class by a pretty large margin if you consider one line:
val name = new Property[String](null)

Instead of three (or seven in the case of Java):
private var _name: String = _
def name = _name
def name_=(name: String) = _name = name

However, the typical response here is that with today's IDEs they will generate the getter/setters for you automagically, so what does that matter? To some degree I agree with this, but it is still clutter added to your classes that make managing your source code that much more complicated down the road. I will agree though that this in and of itself is not a convincing reason to break convention so lets get on to the real reason.

Ultimately it comes down to one major reason to use the Property concept instead of methods, and that is encapsulation. Given the above Person example with a "name", how would you handle a check upon assignment to make sure the name is no more than 255 characters, and if it is, to chop off the extra characters?

Perhaps something like:
class Person {
    private var _name: String = _

    def name = _name

    def name_=(name: String) = {
        val filtered = ...filter name...
        _name = filtered
    }
}

That's simple enough, but what if you want to add an "address" field as well and limit it to 255 characters? Sure, you can simplify by creating a "filter" method you can invoke, but no matter how you swing it, you end up with boiler-plate code to accomplish your task. The reason this happens is because methods don't have the ability to encapsulate and extend functionality the way that an object does. So for the above example I could simply create a FilteredProperty trait that can be mixed in:
trait FilteredProperty extends Property[String] {
    abstract override :=(value: String) = {
        val filtered = ...filter value...
        super.:=(filtered)
    }
}

Now you simply make a very slight change to your person class to get the benefits of filtering:
class Person {
    val name = new Property[String](null) with FilteredProperty
}

Hopefully you are beginning to see the advantages here. In order to add functionality to your simply extend Property functionality to provide what you want and then mix it together in your resulting Property instance. Not only does this remove any need for boilerplate code, but it makes reading the source code for classes extremely easy as a single line of code describes the make-up of that property.

Here are a few examples of things you can do well with Properties:
  • Events: Adding event listeners to a property is one of the most common uses for a Property system as event handling in a standard Bean scenario involves a lot of boiler-plate code. Here's a simple example of usage:
    person.name.listeners += nameChanged _
    
    private def nameChanged(evt: PropertyChangeEvent[String]) = {
        ... property has changed ...
    }

    As is hopefully obvious, any changes to person.name will result in the invocation of the nameChanged method.
  • Binding: Synchronizing two properties together via binding is probably the second most common reasons Properties are used. This is something that takes quite a bit of effort to support, but by leveraging Properties the functionality can be written once and then re-used everywhere desired. Here's a simple example of usage:
    person1.name bind person2.name

    Sure, that's incredibly simple, but now any changes to person2's name will result in person1's name being updated as well.
  • Translation: The ability to translate incoming values is a very broad topic, whether you are converting Strings to Ints, translating to another language upon set, or simply modifying all applied data to follow a specific standard Properties can provide all of the functionality by simple extension and mix-in.
  • Filtering: Applying a filter may represent limiting of a String to 255 characters as discussed above, or it may include throwing an Exception if invalid data is attempted to be passed (ex. Attempting to set null on a Property that should not be null).
  • Transactions: Simple transaction support may consist of an uncommitted value along with commit and rollback methods. This would allow simple management of objects in a user-interface and the ability to know if the object has uncommitted values and what they are so you are only applying changes to the database instead of sweeping updates.
  • Animation: Also called "adjusters", this is the ability to move from one value to another over time. This is most commonly applied to numbers. For example, a AnimatingProperty trait may start at 0.0 but when the value is set to 100.0 instead of applying it directly, it is applied over time and possibly with an easing function.
  • Delegation: This allows the ability delegate getting/setting functionality to another method, function, or value.

There is a great deal more that can be done with Properties, but this is hopefully a nice glimpse to what is possible.

History


As much as I'd love to claim responsibility for the idea of properties, alas, it is not my own and here are a few URLs to other posts about properties (including some older ones from me):

http://joda-beans.sourceforge.net/
http://www.javalobby.org/java/forums/t88090.html?start=50#92123353
http://www.javalobby.org/java/forums/t90756.html
http://www.javalobby.org/java/forums/m92123242.html
http://www.matthicks.com/2009/06/have-beans-been-holding-us-back.html
http://www.matthicks.com/2009/07/death-of-beans.html

2 comments:

  1. How does this relate to http://doc.akkasource.org/stm-scala
    ?

    ReplyDelete
  2. phil, in many ways the concepts are similar to java.util.concurrent.atomic package but more elegant. Properties in Sgine share many similarities to references as they wrap a value, but the core benefit to Properties is their extensibility and though this may be possible with that system it is not the point of it.

    ReplyDelete

Scala Engine for high-performance interactive applications.