Monthly Archives: May 2012

Telescope to Hell, or An ActionScript Builder Pattern

One of the evils that OOP freed us from was long parameter lists like this:

wm_op(hdl, 0, 0 , 230, false, false, "false", true, ptr.value)

Even with default parameters and the occasional enumeration, telescoping parameter lists like these are chronic bugs waiting to happen and to regress. The problem was that call was not self-explanatory. Using object properties allows us to see each property’s name as we assign it:

var processor : Processor = new Processor(this);
processor.x = 0;
processor.y = 0;
processor.minHeight = 230;
processor.buttonText = "false";
processor.initialValue = pointer.value;

Instead of entering dummy arguments to allow access to later parameters, the object sets its own defaults.

Every Value a Parameter

The Every-Value-A-Parameter (EVAP) style of constructor design seems to forgo much of the strength of objects. It treats objects as data structures and does not take advantage of their properties’ ability to be internally consistent. The EVAP style passes all the internal values of the object in the constructor’s parameters, and all properties are read-only from that point forward (i.e. an immutable object).

If several parameters in a long list interact with each other or are mutually exclusive, either the constructor fails to create the object (often an opaque exception) or we do not check the interaction until needed (e.g. until a query object was activated). We cannot validate the parameters one-by-one because they all come in at once.

The calling code must also have all the information about the object at once.

var foo : Foo;
if (ref.gah >= 14)
   foo = new Foo(this, ref.gah, null, (container == null) ? null : container.bar);
else
    foo = new Foo(this, 2, null, (container == null) ? null : container.bar);

As shown, a symptom of  EVAP is a mass of trinary expressions (exp ? true : false)  in call parameters, or branches with alternate initializers for the same object. If one could set the new object’s attributes explicitly and individually (i.e. after it was instantiated), then the code branches would be specific to the attributes they affect.

var foo : Foo = new Foo (this);  

if (ref.gah > 14)
   foo.gah = ref.gah;
else
   foo.gah = 2;

if (container != null)
   foo.bar = container.bar;

A subtler aspect is the ability to allow descendant classes, containers, or helpers to override an object’s configuration. For example, a base query object might have a base query string, but a descendant class or a wrapper class, OracleSQLQuery, might take advantage of Oracle-specific syntax or settings. If the query string must be a read-only constructor parameter, the descendant cannot override an contained object created by its ancestor; at best, it can create a new object, copy every other attribute value, and assign it to a protected reference that the ancestor will (we hope) use.

Special Mutators for Mutually Dependent Properties

Some parameters’ validity depends on other parameters (e.g. height and minHeight). An object can adjust one to fit another, but this can violate a  object’s agreement with the user: a writable parameter value will be what the user set it to.

form.height = 95;
form.minHeight = 80;
...
form.height = 75;
form.minHeight = 70;

If the object unilaterally and silently changes the second value for height to 80 (the minimum), the coder will expect it to be 75. If the object rejected the assignment because it (at the time) violated the minHeight value, then the object will remain internally consistent.
Some properties are mutually dependent, so it is more than a matter of the sequence of assignment. If the object cannot defer the dependency validation (see below), then it can offer a special mutator-like function that accepts both or all of the mutually dependent values at once. Two or three parameters are not too obtuse, especially if the function names them explicitly.

foo.setNameAndPassword(...)
foo.setPosition(x, y);

Deferred or Explicit Validation

A SQLQuery object can allow any value for its queryText and parameters properties until the moment its active property turns true. At that moment, all of the properties must be consistent with each other (i.e. the items in the parameters collection must match the references in the queryText). If something tries to change one of these values in an active query, the object must decide whether to set active to false or to reject the new value (e.g. by throwing an exception).

Another example might be a class URIString, which does not check for validity until something calls function isValid(). The user can set and clear different, mutually-exclusive parts of a URI without raising an exception because their interrelations don’t matter until the validation method runs.


Jeffrey Stylos and Steven Clarke: “Usability Implications of Requiring Parameters in Objects’ Constructors.”ICSE 2007.

Immutable Objects and Internal Consistency

Nevertheless, some objects really want to be immutable. It saves a lot of checking and binding events, allows static caching, and removes the need for a .copy() or .clone() method.

The best of both wolds is an immutable object with a (mutable) builder. The builder object takes all of the initial values as interactive, public properties, optionally validates those values when they are set or just before the builder becomes the immutable constructor’s sole parameter. One might use it like this:

    var builder : Object = ImmutableObject.createBuilder();
    builder.name = "Fred";
    builder.birthMonth = 4;
    builder.birthYear = 1960;

    var imm : ImmutableObject = builder.create();

The immutable class and the builder class work together:

  1. The class’ static function returns an instance of the builder class
  2. The consumer plays with the builder’s properties
  3. The consumer has the builder create an instance of the class
  4. The builder validates its properties
  5. The builder passes itself to the class’ constructor
  6. The class initializes its immutable properties (i.e. the private fields) from the builder’s properties
  7. The builder passes the new class instance to the consumer

Instead of creating a public builder class for each immutable class and ending up with lots of little class files, we can use an internal class with some casting. Note that while the client code does not have access to the ImmutableObjectBuilder class, it gets an instance of that class (into its Object-type variable), and it cannot treat it like a generic object (e.g. builder.height = 44; gives an error).

public class ImmutableObject
{
    public function ImmutableObject(builder : Object)
    {
        if (builder is ImmutableObjectBuilder)
        {
            builder.validate();

            initialize(ImmutableObjectBuilder(builder));
        }
        else
            throw new Error("use ImmutableObject.createBuilder().create()");
    }

    private function initialize(builder : ImmutableObjectBuilder) : void
    {
        _name = builder.name;
        _address = builder.address;
        _birthDate = new Date(builder.birthYear, builder.birthMonth);
        _photoUrl = builder.photoUrl;
    }

    public static function createBuilder() : ImmutableObjectBuilder
    {
        return new ImmutableObjectBuilder();
    }

    public function get name() : String
    {
        return _name;
    }
    private var _name : String = "";

    public function get address() : String
    {
        return _address;
    }
    private var _address : String = "";

    public function get birthDate() : Date
    {
        return _birthDate;
    }
    private var _birthDate : Date;

    public function get photoUrl() : String
    {
        return _photoUrl;
    }
    private var _photoUrl : String = null;
}

class ImmutableObjectBuilder
{
    public var name : String = "";
    public var address : String = "";
    public var birthMonth : int;
    public var birthYear : int;
    public var photoUrl : String = null;

    public function validate() : void
    {
        if ((name == null) || (StringUtil.trim(name) == ""))
            throw new Error(""name" is required");

        if ((birthMonth == 0) != (birthYear == 0))
            throw new Error("Both birthMonth and birthYear must be set");
    }

    public function create() : ImmutableObject
    {
        return new ImmutableObject(this);
    }
}

The downside to this pattern is that it is a little tricky to subclass.[EXPAND Click to see how]
Using the ImmutableObject shown above, the descendant class has to get an instance of the ancestor’s builder and give that reference to its builder. The descendant builder cannot descend from the ancestor build because they are both internal classes.

The descendant builder has to proxy or shadow all of the ancestor builder’s properties so the descendant builder can use its reference to the ancestor builder to initialize the ancestor part of the descendant instance. I said it was a little tricky.

In this example, the descendant builder proxies properties (e.g. name, address) to the ancestor builder’s properties, and adds one new one: role.

public class Descendant extends ImmutableObject
{
    public function Descendant(builder : Object)
    {
       if (builder is DescendantBuilder)
       {
           builder.validate();

                   // this calls the ancestor's initialize()
           super(builder.ancestorBuilder); 

                 // we have to cast because the parameter 
                 // cannot be the internal class DescendantBuilder
           initialize(DescendantBuilder(builder));  
        }
        else
            throw new Error("use Descendant.createBuilder().create()");
    }

    public function get role() : String
    {
        return _role;
    }
    private var _role : String;

    //  we can't virtualize this method because
    //     we won't have access to the AncestorBuilder
    private function initialize(builder : DescendantBuilder) : void
    {
        _role = builder.role;
    }

    //      static methods are not inherited, so we do not override
    public static function createBuilder() : DescendantBuilder
    {
        var ancestorClassName : String = getQualifiedSuperclassName(Descendant);
        var ancestorClass : Class = Class(getDefinitionByName(ancestorClassName));
        var ancestorBuilder : Object = ancestorClass.createBuilder();

        return new DescendantBuilder(ancestorBuilder);
    }
}

class DescendantBuilder
{
    public function DescendantBuilder(ancestorBuilder : Object)
    {
        _ancestorBuilder = ancestorBuilder;
    }

    public function get ancestorBuilder() : Object
    {
        return _ancestorBuilder;
    }
    private var _ancestorBuilder : Object;

    public function get name() : String
    {
        return _ancestorBuilder.name;    //    proxy
    }

    public function set name(value : String) : void
    {
        _ancestorBuilder.name = value;
    }

    public function get address() : String
    {
        return _ancestorBuilder.address;    //    proxy
    }

    public function set address(value : String) : void
    {
        _ancestorBuilder.name = value;
    }

    public function get birthMonth() : int
    {
        return _ancestorBuilder.birthMonth;    //    proxy
    }

    public function set birthMonth(value : int) : void
    {
        _ancestorBuilder.birthMonth = value;
    }

    public function get birthYear() : int
    {
        return _ancestorBuilder.birthYear;    //    proxy
    }

    public function set birthYear(value : int) : void
    {
        _ancestorBuilder.birthYear = value;
    }

    public var role : String = null;    //    NOT a proxy

    public function validate() : void
    {
        _ancestorBuilder.validate();

        if ((role == null) || (StringUtil.trim(role) == ""))
            throw new Error(""role" is required");
    }

    public function create() : Descendant
    {
        return new Descendant(this);
    }
}

And the obligatory sequence diagram: 

[/EXPAND]