Report and session by Kevlin Henney
The BLOCK idiom was introduced as a functional object pattern applicable in Java 1.1, and as a language specialisation of the COMMAND design pattern. The area of identity-transparent patterns proved to be the most fruitful, with a short form of the NULL OBJECT pattern, much discussion of SINGLETON, and the presentation of IMMUTABLE VALUE. Acquisition and release patterns abstract a common form of control flow and, into the bargain, ensure exception safety. A pure control-structure-based approach, TRY EVERYTHING, resolves common exception blindspots, whereas EXECUTE AROUND METHOD is a useful import from Smalltalk which may also be extended to COMMIT OR ROLLBACK. There is perhaps more to iteration patterns than is sometimes realised, and beyond the mapping of ESSENTIAL ITERATOR and the realisation of POLYMORPHIC ITERATOR in java.util.Enumeration, ENUMERATION METHOD proves to be another expressive import from Smalltalk.
The session drew to a close after some brief conclusions.
There are many types of pattern. To name a few:
The focus here is on idioms, in particular for Java. All patterns are described as having a context in which they are applicable; for idioms the language is a part of this context. Idioms are, literally, what the locals speak. In this case techniques applied by the users in a programming language culture. They help to stabilise language usage and create a common vocabulary of techniques, constraining the potentially infinite possibilities of a language grammar and semantics. For instance, whilst the expression i = i + 1 is a well formed one in Java, C and C++, it is not the idiomatic way of expressing an increment: ++i or i++. When initially learning a language part of the challenge is to learn the syntax and semantics of the language; another part, as with learning a natural language, is to develop a familiarity with the common way in which it is used and how best to express oneself.
Just as with natural language, it is possible that programmers 'speak' a new language with an accent, e.g. writing Java with a C++ accent, or writing C in a FORTRAN style (C-Tran). Some idioms, such as those for choosing expressive identifier names and writing clear, can be transferred easily across languages. Others depend on features of a language model and are simply inapplicable when translated, such as a strong and statically checked type system (e.g. C++ and Java) versus a looser, dynamically checked one (e.g. Smalltalk and LISP). For instance, the DETACHED COUNTED HANDLE/BODY pattern describes a reference counting mechanism for C++, the need for which is obviated in Smalltalk and Java by the presence of automatic garbage collection.
As well as styles and idioms that are native to Java, of interest are those that can be imported from other languages, such as Smalltalk and C++, and language models, such as functional programming. There are also patterns that arise that seem similar in some way to those found in other languages and solve the same problem, except that the roles or the interactions of the participants seem to be inverted; this is the case with the acquisition and release patterns RESOURCE ACQUISITION IS INITIALISATION (C++) and EXECUTE AROUND METHOD (Smalltalk).
Languages that treat functions as first class objects, and to some extent even those that support them as second class objects (e.g. function pointers in C and C++), support this capability directly. Alternatively, such functions can be reified and modelled indirectly as objects. This is the typical way that such behaviour is expressed in any language supporting OO, and the only way for some languages such as Java. Functional object patterns are behavioural patterns that cover this area.
| Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. |
Here is a simple Java interface for general purpose actions:
interface Command
{
void execute();
}
Implementing classes provide concrete functionality. Command can also be extended to describe actions with additional capabilities, such as archiving, storing and forwarding, serialising, and transactional commit and rollback. A command undo system, as found in many GUI applications such as editors, can be implemented using this pattern:
interface UndoableCommand extends Command
{
void undo();
}
Java does not support blocks, but in Java 1.1 inner classes were added to the language. An inner class is a class whose definition is nested within another class or one of its methods. They can access the scope of their enclosing class, which means that an instance of an inner class can access the variables of the instance of the enclosing class that it was created from. In the case of inner classes defined within methods, instances can also access any local variables declared as final.
Inner classes support a wide range of uses and styles. Of particular interest here is the further refinement that anonymous inner classes can be defined inline as part of an expression. All that is needed is to specify, in a new expression, the interface or class that the anonymous class implements or extends and then provide the definition body.
The BLOCK idiom for Java uses anonymous classes with a single method to support a Smalltalk-like closure as a variant of the COMMAND pattern. Consider a timer class that takes an interval and a command object to execute on expiry of the interval:
class Timer ...
{
public Timer(int when, Command what) { ... }
void start() { ... }
...
}
We can easily set up a timer to execute an ad hoc task by applying BLOCK:
new Timer(delay,
new Command() {
public void execute() { block of code }
}).start();
Its essence is that you invert the relationship between control and data, thus simplifying control flow structure. Given clunky procedural code like
if(object != null)
object.doSomething();
Then always ensure that the object reference is never null, and for those null cases ensure that it refers to an object that implements null behaviour. This results in more direct code like
object.doSomething();
The form of the pattern presented during the session was interesting in terms of the forces that it itself resolved, and caused some discussion offline! The constraints on the pattern form were that it should be concise and fit on a single slide. An interesting non-functional requirement that results in a difference between presentation and full form comparable to the difference between whiteboard code and production code.
NULL OBJECTif
|
The production rule form is suitable for simple patterns, such that the forces, context and problem can be folded into the precondition, and the solution action and resulting context can be described as the consequent or postcondition. As with a pattern it is worth stressing the applicability for such a form: the pattern should be composed of simple parts.
Some developers assume that since there are many objects that happen to have one instance in a particular program, they should apply SINGLETON and therefore a global point of access. SINGLETON is not for expressing objects that happen to have a single instance, but for those that must have the number of their instances controlled for some designs there may be more than one, e.g. the requirement to have a single object of a particular type per thread can be managed by applying SINGLETON. The SINGLETON pattern is not simply about constraining instance numbers; it also encapsulates and defers the creation of instances until the point of demand (LAZY EVALUATION). This is useful where it would be wasteful to create an instance outright (e.g. in a static initialiser) or difficult (e.g. an object per thread).
Another suitable application of SINGLETON is as an optimisation in cases where the identity of an object is not an issue, and there is no variation across instances of a class. As a null object is stateless it can often be a singleton:
final class NullCommand implements Command
{
public static Command instance()
{
if(singleton == null)
singleton = new NullCommand();
return singleton;
}
public void execute() {}
private NullCommand() {}
private static Command singleton;
}
Notice that methods on a singleton are instance methods; only the instance access is a static. A common misimplementation is to assume that the class is the instance and have all methods as static. This emulates a module and not a singleton sometimes such classes are described as suffering from modulitis. Whilst there are certainly applications for this technique (e.g. java.lang.Math), they are certainly nothing to do with SINGLETON: SINGLETON is an object creational pattern, not a class pattern, and as such describes the transparent use of instances; a reference to a singleton may be passed around; a singleton may participate in an inheritance hierarchy (as in the case of NullCommand). Therefore, other than access, the use of SINGLETON should not significantly distort either the implementation of a class or its use.
There was a great deal of discussion about some of the points made, confirming that far from being a simple and obvious pattern SINGLETON has many subtleties lurking under the hood. In essence it is not genuinely suitable as an introductory pattern, and is better introduced in the context of implementing other patterns such as NULL OBJECT and ABSTRACT FACTORY. Perhaps it it should also come with a health warning to ward off casual users!
Glibly put, we can summarise the pattern as follows:
Objects with modifiable state...
|
A case of "Doctor, doctor! Every time I do this it hurts!", "Well, don't do it."! Although this captures some of the solution, it does not fully capture the detail that makes this a pattern, i.e. context, forces, etc. Cast into a fuller, Coplien pattern form it is rendered as follows:
Name
Problem
Context
Forces
Solution
Rationale
Resulting Context
Examples and Related Concepts
|
Note that the IMMUTABLE VALUE pattern should not be confused with a READONLY INTERFACE, which seeks to separate the modifier and query operations on a class into separate interfaces so that it is clear what role an object is permitted and intended to play when passed to another as a method argument. Such a technique enforces specification requirements more clearly in the type system. This can be considered to emulate many uses of const in C++, constant in Ada 95, and READONLY in Modula-3 when these are applied to arguments. Note that Java's final does not have this effect on method arguments: for object references it simply ensures that an object reference is not reassigned in the scope of the method and says nothing about the fate of the object.
The difference between IMMUTABLE VALUE and READONLY INTERFACE is fundamental: IMMUTABLE VALUE addresses issues related to concurrency and aliasing for objects whose value but not identity is important; READONLY INTERFACE addresses partitioning a class interface into separate interfaces based on method side effects to support finer grained specification of intent. Not only is the intent, motivation and applicability different, but also the resulting context: for example, an IMMUTABLE VALUE class is typically a full implementation and should either be defined as final or must require that subclasses are also subtypes, i.e. they also implement IMMUTABLE VALUE; there is no such consequence for READONLY INTERFACE.
IMMUTABLE VALUE can in some ways be considered a specialised application of READONLY INTERFACE. READONLY INTERFACE may also be used to resolve the Circle-Ellipse problem and support modifiable circles and ellipses in a consistent subclasssubtype hierarchy.
A note on the Circle-Ellipse problem:
This is the problem that comes from taking the apparently intuitive step from "a circle is a
kind of ellipse" to having a circle inherit from an ellipse. Leaving aside the issue of
redundant state in the subclass, this appears to present a problem when, given an ellipse that
supports general resizing, a circle is stretched along an axis: it ceases to be a circle, and
yet still retains the compile-time type of a circle. The resolution is to realise that the translation from
"a circle is a kind of ellipse" is imperfect: "so long as it is not modified, a circle is a
kind of ellipse". Put another way, when ellipse and circle are modelled as immutable values
the problem disappears. This illustrates that the problem is simply one of incorrect modelling:
the domain (mathematics) does not directly model circles and ellipses as referenced mutable
entities!
Two of the more common acquire and release resources memory and thread synchronisation are handled within in the language: garbage collection (GC), and synchronized methods and blocks. However, relying on the GC system and a call to finalize may be as ineffective as it is dangerous; it is something of a Java myth that these two features will solve all problems for all systems. The lack of determinism is not the only handicap: the principal assumption in GC is that the only resource in need of collection is memory. Although a programmer is relieved from many of the common worries associated with explicit memory management, the GC system does not address exhaustion of other resources such as file handles: failure to acquire a system resource does not trigger the GC to run and collect any discarded objects that may be still be hogging resources.
Given that GC and synchronized do not address all acquisition and release issues, and that files must still be opened and closed, cursors set and reset, etc. there is still a genuine need to understand acquisition and release idioms.
Therefore it is not safe to assume that an acquire and a release can both appear in the same block even when neither method has a throws clause:
{
resource.acquire();
usage code
resource.release();
}
{
resource.acquire();
try
{
usage code
}
finally
{
resource.release();
}
}
The EXECUTE AROUND METHOD pattern is a Smalltalk import that may be applied in Java 1.1 onwards. The code for execution is passed to the resource itself for execution, typically by applying the BLOCK pattern:
resource.executeAround(
new Command() {
public void execute() { usage code }
});
public synchronized void executeAround(Command cmd)
{
acquire();
try
{
cmd.execute();
}
finally
{
release();
}
}
It is assumed that the usage code refers to resource explicitly. Alternatively the execute method can take an argument to have the resource passed in.
interface CommitableCommand extends UndoableCommand
{
void commit();
}
public void execute(CommitableCommand cmd) throws RolledBack
{
synchronized(cmd)
{
try
{
cmd.execute();
cmd.commit(); // only reached if successful
}
catch(Error caught)
{
cmd.undo();
throw caught; // should repropagate errors
}
catch(Throwable caught)
{
cmd.undo();
throw new Rolledback();
}
}
}
A common, but unsuccessful, solution is to embed a cursor inside the collection. This supports only single traversal and placeholding at a time. This tends to be impractical, and is also a weakly-cohesive design. The collection now has two distinct responsibilities and seems to be two distinct objects: the first is a collection, and the second offers a set of facilities to manage iteration of the collection. For some collections such stream-like behaviour is appropriate, but for the majority the intrusion on the logical and physical state is not. Iteration patterns are behavioural patterns that seek to resolve the forces described.
Note also that these facilities may be mapped one for one with operations, or they may be folded into fewer operations that involve side effects.
An iterator is in many respects an abstract reference as it may be used to hold a position within a collection. It acts as an access pattern rather than an algorithmic one; it is used within algorithms for queries and manipulation but does not itself encapsulate any control flow.
An interface defines the basic protocol for iteration. The standard java utility library, java.util, provides one such interface: perhaps a little confusingly, it is called Enumeration. The JGL (Generic Collection Library for Java) provides an additional set based on the iterator model described in C++'s STL.
java.util.Enumeration has a simple completion query, hasMoreElements, and a method that combines query of current item with advancing, nextElement. An implementing class provides these, and a concrete collection is responsible for the creation and initialisation of the correct iterator type, giving rise to a parallel hierarchy and creation relationship that is also an instance of the FACTORY METHOD pattern. The creation method is typically named after the concept it gives access to, e.g. elements for accessing the elements stored in an implementation of java.util.Dictionary.
An iterator class is naturally, and closely, coupled to its collection class as it depends on its representation. In Java 1.0 the only way that a collection class could grant representation access to the its iterator class was to ensure that the relevant features were visible at the package level, but still private to users in other packages. The inner and static class features of Java 1.1 provide a much better route for implementing iterators.
|
How do you provide safe, general access to collection elements?
Implement a method that executes a block for each element of the collection. |
This has the effect of keeping the iteration within the collection but, unlike the stream-like approach, this encapsulates a traversal in a single method and has no impact on the logical or physical state of the collection. This ensures that the knowledge of the collection's representation is confined to a single class.
A COMMAND interface can be offered for use with a collection class or group of collection classes:
interface ForEach
{
void executeOn(Object element);
}
collection.forEachDo(
new ForEach() {
public void executeOn(Object element) { action to perform on element }
});
The question arises as to when to use ITERATOR and when to use ENUMERATION METHOD. Their intent and consequences are slightly different: ENUMERATION METHOD is a control-flow pattern in that it encapsulates an algorithm, and a collection may offer the most common forms of collective usage in a method category with various implementations of ENUMERATION METHOD; ITERATOR abstracts a reference to a position within a collection and may be used to implement algorithms that are no genuinely part of the collection's remit, and which therefore should not clutter its interface. ENUMERATION METHOD can also be combined transparently with acquisition and release patterns to ensure transactional and threadsafe behaviour.
In Smalltalk there is no real decision to make as the language does not offer sufficient control over visibility in the way that Java has with packages and inner classes to ensure that two tightly coupled classes can be implemented without repealing the layer of encapsulation to all other classes. Java offers the flexibility of using either. The relationship between the two patterns is one of inversion, i.e. the responsibilities of the participants have effectively been reversed to achieve the goal of iteration.