Programmatic Design Patterns

A design pattern is a "solution to a problem in context"; that is, it represents a high-quality solution to a recurring problem in design.  In software engineering, design patterns are commonly used patterns of source code used to achieve particular goals in a particular context.  Programmatic Design Patterns allow automatic implementation of existing design patterns in new code in a clean, object-orientated fashion.

The bean property design pattern

A simple example of a design pattern in Java is the commonly used bean property design pattern.  This section of Java source code shows an implementation of this design pattern:

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

This code uses the bean property design pattern to declare a property called 'name' of type String whose value can be set or retrieved.  This pattern is used so that, unlike with fields, code can be executed when setting or retrieving property values.  It follows that the property value need not be stored in a field, can be derived lazily, setting the property can fire events etc. etc. 

There are several problems with using design patterns in this way:

More fundamentally, using patterns is not declarative programming.  In the above code, you are thinking "I want an editable property called 'name' of type String" but you are writing "I have a field called 'name' of type String and a method called getName which returns...etc.".  Declarative programming states that you should write exactly what you mean.

The Bean Property as a Jura Programmatic Design Pattern

In Jura, the bean design pattern is available as a Programmatic Design Pattern, called Property.  This is just an ordinary class which implements the Pattern interface.  You import the pattern just like importing any other class and use it directly within your class definition.  At compile time, the pattern will turn into the corresponding set of fields and methods. For convenience, Programmatic Design Patterns are often be referred to simply as Patterns.

Property:name{type=String}

The use of a Pattern here has led to a simple, clean, declarative piece of code.  Now suppose we want to run some code each time the property value is set or retrieved.  If necessary, we could add it like so, by using the getter and setter properties of Property:  These are just properties of type Block (a block of statements) which the pattern inserts into the generated methods.  

Property:name{type=String
  getter={
    System.err.println("Getting value of 'name' as "+super.get());
    Return{super.get()}
  }
  setter={
    System.err.println("Setting value of 'name' to be "+newName);
    super.set(newName);
  }
}

This looks less elegant but is okay as a once-off piece of code.  However, this is the sort of debug code that is likely to be used all the time and so would be built in to a well-designed Property pattern, so that you could use the following code instead:  

Property:name{type=String debug=true}

The Property design pattern would then add appropriate debug statements to the generated methods. Alternatively, you could extend Property to create a new pattern with the required additional functionality.

As I hope this example illustrates, even simple programmatic design patterns like Property can become quite powerful and useful as they are extended to provide additional details about the property.   For example,  whether the property is transient, whether it fires change events, additional details such as display name or icon could all be added to the Property design pattern.  Alternatively, the design pattern class itself can be extended to provide, for example, a DerivedProperty (whose value is calculated from other properties) or an IndexedProperty.

Note: Because Programmatic Design Patterns are expanded into standard Jura code at compile time, they are completely interoperable with existing code that uses ordinary design patterns.

Creating a new Programmatic Design Pattern

We will now see an example of creating a simple Property pattern.   We start by providing an example of the pattern in Jura.  

Field:name{type=String access=protected}

Method:getName{returns=String access=public
  //
  Return{name}
} 

Method:setName(
  Par:name{type=String}
  //
  this.name = name
}

We now look for common elements (in this case, the name and type) and replace these with the variables name, type, and cname for where the name is capitalised.  These variables are marked with a dollar to distinguish them from ordinary variables/properties, as will be explained shortly.  The way the names are set has to be changed from the shorthand colon notation to setting the name as an attribute.  This is necessary because each name is now the result of an expression, rather than a fixed value.

Field{name=$name type=$type access=protected}

Method{name="get"+$cname returns=$type access=public
  //
  Return{$name}
} 

Method{name="set"+$cname
  Par{name=$name type=$type}
  //
  this.$name = $name
}

To create our pattern, we now create a class which implements the Pattern interface. This interface has a single method toJura(TransformContext c) which must be implemented by the pattern and which must use the context to add the statements which make up the pattern.  The pattern also implements Member (which indicates that it can be added directly to a Class or Interface) and Variable (which indicates that it can be used as a variable in an expression).  The dollar now indicates the popup operator, which means "execute what follows as if it were outside the current 'open' expression".

location=jura.pattern

Class:Property{ implements={Pattern Member Variable}

  Field:name{type=String}
  Field:type{type=Type}

  Method:toJura{
    Par:context{type=TransformContext}
    //
    Local:cname{type=String}=Utils.capitalise(name)
    context={
      Field{name=$name type=$type access=protected}

      Method{name="get"+$cname returns=$type access=public
        //
        Return{$name}
      } 

      Method{name="set"+$cname
        Par{name=$name type=$type}
        //
        this.$name = $name
      }
    }
  }
}

Of course, this is a fairly minimal implementation.  The implementation in the prototype allows for inserting statements in the methods (as in the example above), setting the access level of the property (e.g. protected rather than public), adding documentation and making the property read-only, final or transient. 

Using Patterns to extend the Jura language

For simplicity and clarity, Jura provides a fairly minimal set of language features. However, almost any language feature can then be added by importing Patterns as needed. In fact, a few basic language features are implemented internally as Patterns. Commonly requested features that can be provided using Patterns include:

The ability to add language elements when they are needed means that the Jura language can be easy to understand and learn whilst also allowing code to be concise and declarative.

Note: The patterns ForEach, IfCast, Property and Delegate are implemented in the current prototype (Jura 0.8).