Saturday, December 06, 2008

The Properties Pattern and Functors

This week I have nothing to write about - I am in various stages of completion on multiple things I started, but none complete enough to post. Accordingly, in true blogging tradition, I do the next best thing - find someone else's blog, and comment on it, thereby creating a post of my own :-). This week's lucky winner is Steve Yegge's "The Universal Design Pattern" post. Granted, the post is long, but it is very interesting and informative - and entertaining. I strongly suggest you go read it first.

If you did as I suggested, you would know that Steve's Universal Design Pattern is the Properties Pattern, or an approach of modeling your object's data as Maps of name-value pairs instead of member variables. I was quite enamored with DynaBeans at one point, so much so that I built a very flexible (but somewhat difficult to maintain) content generation system around it, so this post was a major source of validation for me. I went with DynaBeans and DynaClasses because I wanted an inheritance structure, which I could not figure out how to model with maps at that time - Steve's post describes how to do this, with a special _parent key pointing to the Map that the current map extends.

So here is my take on the Properties Pattern. Each object has a Map of name-value pairs instead of traditional member variables and getters and setters. Instead, there is a get(String) and a set(String, Object) method which get and set the property named by the String argument. A get() will recursively climb the inheritance tree using the _parent key until it finds the value for the key before giving up and returning null.

I think it may be possible to take this one step further. If you look at a Prototype Ajax.Request call, it looks something like this. I choose Javascript because Javascript uses the Properties Pattern very extensively and this particular example illustrates that.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  var request = new Ajax.Request(
    "/path/to/service/url",
    {
      method: 'get', 
      parameters: { 
        a : $F('a'),
        b : $F('b')
      }, 
      asynchronous: true,
      onLoading: function(transport) {
        // do something while the request is processing
      },
      onSuccess: function(transport) {
        // do something when the request is complete
      }
    });

In our example above, the second argument is a Map of name-value pairs. To our Properties Pattern enabled Java application object, this would like like a map with the keys {"parameters", "asynchronous", "onLoading", onSuccess"} with corresponding values hanging off them. All but the last two are simply data, but the last two are really function objects.

Although Java does not provide us ways to instantiate functions directly, we can still attach standard functor classes, such as a Transformer, from commons-collections. The onLoading and onSuccess methods are listeners that get invoked when the state of the enclosing object changes. To emulate that behavior, we could have a check to see if any of the "on*" methods should be invoked before we set the key in our set() call. Something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  private Map<String,Object>> map = new HashMap<String,Object>();
  ...
  public void set(String key, Object value) {
    for (String key : map.keySet()) {
      if (key.startsWith("on")) {
        // shouldFire is application or object specific
        if (shouldFire(key)) {
          Object value = fire(key, data);
          // if value is returned, do something specific with the value.
          // But most of the time (at least in the event handler case)
          // it would just be null.
        }
      }
    }
    map.put(key, value);
  }

  private Object fire(String eventName, Map<String,Object> data) {
    Transformer<Map<String,Object>,Object> handler = 
      (Transformer<Map<String,Object>,Object>) map.get(eventName);
    return handler.transform(data);
  }

It is quite possible that being able to set a function as a property may not be all that helpful, since the behavior of a prototype in most situations is directed through code -- the behavior may be slightly modifiable using data. Allowing functions to be specified in the property map means that the new instance may have completely different (overridden) behavior from its parent prototype. While this may not be the desired behavior in most cases, there can be situations, where you want to have different behavior in the child than in the parent, and being allowed to override or add functionality via function objects can be helpful.

The problem of serialization and user-friendliness can be tackled together by "allowing" the user to specify the functions as scripts in interpreted languages such as Jython or Javascript, which also run in the JVM through the ScriptEngine interface available since Java 6. Since they are scripts, they can be serialized and deserialized as text or JSON if needed.

No comments:

Post a Comment

Comments are moderated to prevent spam.