you think about programming, is not worth knowing.
— Alan J. Perlis
This chapter presents the individual "Gang of Four" pattern implementations and corresponding evaluations. The patterns are presented in the order defined by Gamma et al. To keep this thesis as short as possible, the detailed pattern evaluations are kept to a minimum using the evaluation format defined in the evaluation approach. Each pattern has a dedicated section that presents its Intent, Structure, Participants, and Implementation, using familiar "Gang of Four" pattern element names. We present the pattern structure as a detailed UML class diagram that includes information about relevant attributes and operations. The UML diagrams also identifies the pattern participants, which are described thereafter, linked to the developed Java classes. An Implementation section addresses all functionality described by Gamma et al. in the Implementation and Sample Code elements for a given pattern, and illustrates whether or not the functionality can be implemented in Java 6. The paragraph headings in the Implementation sections are taken from the corresponding topics discussed by Gamma et al. in the pattern descriptions. A brief summary concludes this chapter.
8.1.1. Abstract Factory
Abstract Factory is a Creational pattern with Object scope. The implementation is located in the dk.rode.thesis.abstractfactory package.
8.1.1.1. Intent
Provide an interface for creating families of related or dependent objects without specifying their concrete classes [Gamma95, p.87].
8.1.1.2. Structure
Figure 8.1 illustrates the Abstract Factory implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.1 — Abstract Factory UML Class diagram | |
8.1.1.3. Participants
Table 8.1 describes the Abstract Factory participants in the words of Gamma et al. [Gamma95, p.89] and lists the corresponding implementations developed in the evaluation.
Table 8.1 — Abstract Factory participants | |||
Participant | Description | Implementation | |
AbstractFactory |
| AbstractionFactory<E>, GeneratorFactory<E,P>, SequenceFactory<E,P>, PrototypicalFactory<T>, factorymethod.Factory<T> | |
ConcreteFactory |
| PrototypicalRegistry, StandardFactory<E,P>, StandardAbstractionFactory<E>, SynchronisedAbstractionFactory<E>, MemorizableAbstractionFactory<E>, PrototypicalAbstractionFactory<E>, CollectionValueFactory<E>, RangeValueFactory, PrototypicalSequenceFactory<E>, sub—classes of Factory<T> and PrototypicalFactory<T> | |
AbstractProduct |
| bridge.SequenceAbstraction<E>, bridge.SequenceValueGenerator<E>, meta.model.Sequence<E> | |
ConcreteProduct |
| facade.FibonacciSequence, and all bridge.SequenceAbstraction<E> and bridge.SequenceValueGenerator<E> implementations | |
Client |
| Main |
8.1.1.4. Implementation
Factories as singletons — Design choice. Creating the products — Factory Method is used in all implementations of SequenceFactory<E,P>. Prototype usage is illustrated in PrototypicalFactory<T>. A prototypical registry capable of creating any number of prototypical products is implemented as PrototypicalRegistry. Type literals are preferred over class literals to create products because they support generic types, for example using factorymethod.Factory<T>. Defining extensible factories — Parameterised product creation is illustrated in factorymethod.CommandCreator<E,T> and GeneratorFactory<E,P>. The first illustrates use of arguments to determine the product type, while the latter use arguments required by the product type. The PrototypicalRegistry class shows how to create unrelated product types in a type safe manner that does not require an unsafe down—cast as in C++.
8.1.2. Builder
Builder is a Creational pattern with Object scope. The dk.rode.thesis.builder package contains the implementation.
8.1.2.1. Intent
Separate the construction of a complex object from its representation so that the same construction process can create different representations [Gamma95, p.97].
8.1.2.2. Structure
Figure 8.2 illustrates the Builder implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.2 — Builder UML Class diagram | |
8.1.2.3. Participants
Table 8.2 describes the Builder participants in the words of Gamma et al. [Gamma95, p.98-99] and lists the corresponding implementations developed in the evaluation.
Table 8.2 — Builder participants | |||
Participant | Description | Implementation | |
Builder |
| ExpressionBuilder<E>, ComparableExpressionBuilder<E> | |
ConcreteBuilder |
| StandardExpressionBuilder<E>, StandardComparableExpressionBuilder<E>, CountingExpressionBuilder<E>, CountingComparableExpressionBuilder<E>, TypedExpressionBuilder<E>, TypedComparableExpressionBuilder<E> | |
Director |
| Main | |
Product |
| All interpreter.Expression<E> implementations, including interpreter.TypedExpression<E> types |
8.1.2.4. Implementation
Assembly and construction interface — Design choice. The ExpressionBuilder<E> type builds products in a tree—like structure. Various build methods accept already constructed expressions, while some constructed expressions can be manipulated directly, e.g. interpreter.FlowExpression<E>. Why no abstract class for products? — Design choice. The Builder implementation use covariant return types to express specific types when required. Empty methods as default in builder — Design choice. In our view, however, empty methods constitute a poor design choice that will inevitable lead to problems in form of runtime errors, e.g. null pointers. Empty methods are must better suited for Template Method with primitive operations that do not return a value the context depends on.
8.1.3. Factory Method
Factory Method is a Creational pattern with Class scope. The dk.rode.thesis.factorymethod package contains the implementation.
8.1.3.1. Intent
Separate the construction of a complex object from its representation so that the same construction process can create different representations [Gamma95, p.97].
8.1.3.2. Structure
Figure 8.3 illustrates the Factory Method implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.3 — Factory Method UML Class diagram | |
8.1.3.3. Participants
Table 8.3 describes the Factory Method participants in the words of Gamma et al. [Gamma95, p.108-109] and lists the corresponding implementations developed in the evaluation.
Table 8.3 — Factory Method participants | |||
Participant | Description | Implementation | |
Product |
| command.Command<E> | |
ConcreteProduct |
| All command.Command<E> implementations | |
Creator |
| CommandCreator<E,T>, Factory<T>, TypedFactory<T,P> | |
ConcreteCreator |
| SequenceCommandCreator<E>, ReversibleSequenceCommandCreator<E>, ReflectiveCommandCreator<E>, EvilSequenceCommandCreator<E>, sub—classes of Factory<T> or TypedFactory<T,P> |
8.1.3.4. Implementation
Two major varieties — The CommandCreator<E,T> class provide a default implementation, but requires sub—classes to implement the abstract factory method. This is design choice; sub—classes could simply return null from the implemented factory method and the default would always be used. Parameterised factory methods — CommandCreator<E,T> illustrates parameterised factory methods, including call to super (default) implementations. T is the token used to determine the product type (command) to create. Language specific variants and issues — Type literals are preferred over class literals to create products because they support generic types, for example using Factory<T>. The abstractfactory.PrototypicalRegistry class stores prototypes based on their classes. In Template Method, templatemethod.SequenceTemplate<K,E> illustrates that, unlike C++, it is possible to invoke sub—class hooks from the constructor. Lazy initialisation is used in Facade, which only creates sequences used for calculation on demand in a thread—safe manner. Using templates to avoid sub—classing — The Factory<T> class can create any type in a type—safe fashion, including generic types, providing T supplies an applicable constructor. The abstractfactory.PrototypicalRegistry uses upper bounded type parameters to ensure that all type parameters are copyable since the new operator cannot be invoked on type parameters because of erasure. Naming conventions — Design choice, but important if overloaded factory methods are used. Java cannot overload on generic types, only raw types. The Visitor implementation shows how naming can allow visitation based on type parameters.
8.1.4. Prototype
Prototype is a Creational pattern with Object scope. The dk.rode.thesis.prototype package contains the implementation.
8.1.4.1. Intent
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype [Gamma95, p.117].
8.1.4.2. Structure
Figure 8.4 illustrates the Prototype implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.4 — Prototype UML Class diagram | |
8.1.4.3. Participants
Table 8.4 describes the Prototype participants in the words of Gamma et al. [Gamma95, p.119] and lists the corresponding implementations developed in the evaluation.
Table 8.4 — Prototype participants | |||
Participant | Description | Implementation | |
Prototype |
| Copyable<T>, StrictCopyable<T> | |
ConcretePrototype |
| SymbolSequence and CountdownSequence, but also all other meta.model.Sequence<E> implementations | |
Client |
| Main |
8.1.4.4. Implementation
Using a prototype manager — The abstractfactory.PrototypicalRegistry class is a prototype manager. It also covers the Smalltalk example supplied in the Sample Code element [Gamma95, p.125]. Implementing the clone operation — The SymbolSequence class uses Java's built—in clone facility as well as the prototypical functionality offered by StrictCopyable.copy(). The copy() method used in interpreter.Expression<E> objects may encounter unmanageable cyclic references in expression trees. However, the Expression.asSymbol(Context) method illustrates that Java easily can handle cyclic references in graph traversals, so it is simply a matter of implementation. All Copyable<T> objects supply copy constructors by convention to perform deep—copy. Initialising clones — The Copyable.copy() method is parameter—less, but could employ arguments in a manner similar to factorymethod.Factory<T>. It is a design choice we do not recommend because it shifts focus more towards factories, but at flag to indicate deep copying may be appropriate. A copy of an uninitialised interpreter.FlowExpression<E> instance will have to be initialised before use.
8.1.5. Singleton
Singleton is a Creational pattern with Object scope. The dk.rode.thesis.singleton package contains the implementation.
8.1.5.1. Intent
Ensure a class only has one instance, and provide a global point of access to it [Gamma95, p.127].
8.1.5.2. Structure
Figure 8.5 illustrates the Singleton implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.5 — Singleton UML Class diagram | |
8.1.5.3. Participants
Table 8.5 describes the Singleton participants in the words of Gamma et al. [Gamma95, p.119] and lists the corresponding implementations developed in the evaluation.
Table 8.5 — Singleton participants | |||
Participant | Description | Implementation | |
Singleton |
| DanishAlphabetSequence, NorwegianAlphabetSequence, SimpsonsFamilySequence, SimpsonsAndBouvierFamilySequence, MutatedSimpsonsFamilySequence, SmileySequence |
Though not described as a participant, Gamma et al. thoroughly describe and illustrate the use of singleton registries [Gamma95, p.130-132] in the Implementation element. Hence, singleton registries are also implemented in the evaluation. The name and minimal description of the "pseudo—participants" as listed in table 8.6 below are not defined by Gamma et al.
Table 8.6 — Additional Singleton entities | |||
Name | Description | Implementation | |
SingletonRegistry |
| SingletonRegistry<T> @Singleton | |
ConcreteSingletonRegistry |
| StatelessSingletonRegistry<T>, StatefullSingletonRegistry<T>, LoadableSingletonRegistry<T> |
Notice, that we do not demand that a registry must store the different singleton types.
8.1.5.4. Implementation
Ensuring a unique instance — The DanishAlphabetSequence is implemented as a single enumeration constant. The SimpsonsFamilySequence uses a static method to lazily create the singleton instance in a thread—safe manner that does not require synchronisation. Both rely on automatic initialisation, but only when requested. Of the three problematic issues related to this in C++ [Gamma95, p.129-130], only parameterised singleton methods cannot be done in this fashion. They must be implemented as static synchronised methods. Though a parameterised singleton method is a design choice, we do not recommend it as it obfuscates the Singleton purpose. What happens if the singleton method is invoked again with a different value, for example. Overriding new as in Smalltalk is not possible in Java. Sub—classing the Singleton class — The meta.log.LogFactory class uses a system property to determine the type of log to use. Using conditional statements to decide the class is trivial. Java has no separation between header and object files, so compile—time linking to a different implementation is changing the entire class. The SimpsonsFamilySequence allows for sub—classing of the actual Singleton type, which to our knowledge is a novel approach. It does so by identifying the caller to simulate C++ friends, but in a type—safe fashion. A Singleton Registry as described by Gamma et al. [Gamma95, p.130-132] is defined via the SingletonRegistry<T> interface. The LoadableSingletonRegistry<T> is an implementation that allows for type—safe dynamic loading of singleton types based on class names. The actual singleton creation is deferred to the singleton types themselves and no registration is required. This is clearly more flexible than the canonical implementation.
8.2.1. Adapter
Adapter is a Structural pattern with both Class and Object scope. The implementation is located in the dk.rode.thesis.adapter package.
8.2.1.1. Intent
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that could not otherwise because of incompatible interfaces [Gamma95, p.139].
8.2.1.2. Structure
Figure 8.6 illustrates the Adapter implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.6 — Adapter UML Class diagram | |
8.2.1.3. Participants
Table 8.7 describes the Adapter participants in the words of Gamma et al. [Gamma95, p.141] and lists the corresponding implementations developed in the evaluation.
Table 8.7 — Adapter participants | |||
Participant | Description | Implementation | |
Target |
| meta.model.Sequence<E>, java.util.Iterator<E> | |
Client |
| Main | |
Adaptee |
| Any Sequence<E> implementation | |
Adapter |
| SequenceAdapter<E,T> (using AdapterDelegate<S,T>), IteratorSequence<E> |
8.2.1.4. Implementation
Implementing class adapters in C++ — Java does not support multiple inheritance and thus not class adapters. Dynamic proxies can be used to simulate private implementation as illustrated in the meta.reflect.proxy.ProxyFactory class, but it still requires composition as in object adapters. Pluggable adapters — a) The set of abstract operations to use is a design choice. Sub—class implementation for adapter functionality is used by any adapter that utilises inner classes such as the proxy.SequenceProxyFactory class. b) The SequenceAdapter<E,T> class uses composition and forwards requests to the adaptee (sequence) stored internally. c) The SequenceAdapter<E,T> is parameterised and uses AdapterDelegate<S,T> instances.
8.2.2. Bridge
Bridge is a Structural pattern with Object scope. The dk.rode.thesis.bridge package contains the implementation.
8.2.2.1. Intent
Decouple an abstraction from its implementation so that the two can vary independently [Gamma95, p.151].
8.2.2.2. Structure
Figure 8.7 illustrates the Bridge implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.7 — Bridge UML Class diagram | |
8.2.2.3. Participants
Table 8.8 describes the Bridge participants in the words of Gamma et al. [Gamma95, p.154] and lists the corresponding implementations developed in the evaluation.
Table 8.8 — Bridge participants | |||
Participant | Description | Implementation | |
Abstraction |
| SequenceAbstraction<E> | |
RefinedAbstraction |
| SynchronisedSequenceAbstraction<E>, MemorizableSequenceAbstraction<E> | |
Implementor |
| SequenceValueGenerator<E> SequenceValueCollection<E,C>, SequenceValueSet<E,C> | |
ConcreteImplementor |
| SequenceValueArrayList<E>, SequenceValueHashSet<E>, SequenceValueLinkedHashSet<E>, SequenceValueTreeSet<E>, SequenceValueRange |
8.2.2.4. Implementation
Only one implementor — Use of a single Implementor is a design choice. Java scope rules allow implementations to be hidden from clients or by using anonymous adapters for the Implementor implementation. Creating the right Implementor object — Design choice. Sharing implementors — The Main (test) class in the Bridge implementation utilises the meta.reflect.proxy.ProxyFactory to manage shared implementations. The sharing is handled via the Handle/Body idiom implemented in Java using dynamic proxies. Using multiple inheritance — Java does not support multiple inheritance, but as Gamma et al. also note, this binds the implementation to the abstraction at compile—time. Composition can be used instead as illustrated by the SequenceAbstraction<E> class, which can be either fixed or changeable.
8.2.3. Composite
Composite is a Structural pattern with Object scope. The dk.rode.thesis.composite package contains the implementation.
8.2.3.1. Intent
Compose objects into tree structures to represent part—whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly [Gamma95, p.163].
8.2.3.2. Structure
Figure 8.8 illustrates the Composite implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.8 — Composite UML Class diagram | |
8.2.3.3. Participants
Table 8.9 describes the Composite participants in the words of Gamma et al. [Gamma95, p.165] and lists the corresponding implementations developed in the evaluation.
Table 8.9 — Composite participants | |||
Participant | Description | Implementation | |
Component |
| meta.model.Sequence<E> | |
Leaf |
| Any Sequence<E> implementation | |
Composite |
| CompositeSequence<E>, AbstractCompositeSequence<E>, CharSequenceCompositeSequence CompositeStrategy | |
Client |
| Main |
8.2.3.4. Implementation
Explicit parent references — Design choice; not implemented here. Sharing components — Design choice. Maximizing the Component interface — Design choice. Declaring the child management operations — The Composite implementation opts for type safety over transparency as discussed by Gamma et al. [Gamma95, p.167-168]. Child management is defined in the Composite (CompositeSequence<E>) participant because it can never make sense for Leaf components (Sequence<E>). This makes sense in Java because unlike C++, the instanceof operator provides a safe way to cast to a given type. Should Component implement a list of Components? — Children are stored in the Composite participant. Child ordering — The CompositeStrategy enumeration defines strategies for traversing the composite structure, such as depth—first and breath—first. Caching to improve performance — Design choice. The strategies defined here allow retrieved lists to be reused without the need to traverse the composite structure again. Who should delete components? — Java employs garbage collection. What is the best data structure for storing components? — This is a design choice. AbstractCompositeSequence<E> uses a java.util.RandomAccess list for fast traversal.
8.2.4. Decorator
Decorator is a Structural pattern with Object scope. The dk.rode.thesis.decorator package contains the implementation.
8.2.4.1. Intent
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub—classing for extending functionality [Gamma95, p.175].
8.2.4.2. Structure
Figure 8.9 illustrates the Decorator implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.9 — Decorator UML Class diagram | |
8.2.4.3. Participants
Table 8.10 describes the Decorator participants in the words of Gamma et al. [Gamma95, p.177] and lists the corresponding implementations developed in the evaluation.
Table 8.10 — Decorator participants | |||
Participant | Description | Implementation | |
Component |
| meta.model.Sequence<E> | |
ConcreteComponent |
| Any Sequence<E> implementation | |
Decorator |
| SequenceDecorator<E> | |
ConcreteDecorator |
| AppenderDecorator, DuplexDecorator, UppercaseDecorator |
The SequenceDecorator<E> class is used in several other pattern implementations as well, including Iterator, Proxy, and Visitor.
8.2.4.4. Implementation
Interface conformance — Implementing a common interface is sufficient in Java; inheritance is not required. SequenceDecorator<E> implements Sequence<E> and inherits meta.model.AbstractSequence<E>, but can decorate any sequence type. Omitting the abstract decorator class — Design choice. Keeping Component classes lightweight — Design choice, but one that cannot always be controlled in case API classes, for example, have to be decorated. Changing the skin of an object vs. changing the guts — Design choice. Abstract decorator types can make it easier to implement multiple concrete decorator types as illustrated with the abstract SequenceDecorator<E> class.
8.2.5. Facade
Facade is Structural with Object scope. The dk.rode.thesis.facade package contains the implementation.
8.2.5.1. Intent
Provide a unified interface to a set of interfaces in a sub—system. Facade defines a higher—level interface that makes the sub—system easier to use [Gamma95, p.185].
8.2.5.2. Structure
Figure 8.10 illustrates the Facade implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.10 — Facade UML Class diagram | |
8.2.5.3. Participants
Table 8.11 describes the Facade participants in the words of Gamma et al. [Gamma95, p.187] and lists the corresponding implementations developed in the evaluation.
Table 8.11 — Facade participants | |||
Participant | Description | Implementation | |
Facade |
| MathFacade | |
Subsystem Class |
| AckermannSequence, FibonacciSequence, RandomSequence, UnboundedRandomSequence, iterator.IterableSequence<E>, state.ReversiblePrimeSequence |
8.2.5.4. Implementation
Reducing client/sub—system coupling — Design choice, but may require factory creation. The MathFacade class is implemented as a Singleton. Public vs. private sub—system classes — Design choice. Packages in Java can provide encapsulation and implicitly information hiding (access modifiers) for sub—systems. MathFacade, FibonacciSequence, and RandomSequence represent public classes, while AckermannSequence and UnboundedRandomSequence are private sub—system classes and are as such declared package private. Sub—system classes from other packages must be public to be utilised.
8.2.6. Flyweight
Flyweight is a Structural pattern with Object scope. The dk.rode.thesis.flyweight package contains the implementation.
8.2.6.1. Intent
Use sharing to support large numbers of fine—grained objects efficiently [Gamma95, p.195].
8.2.6.2. Structure
Figure 8.11 illustrates the Flyweight implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.11 — Flyweight UML Class diagram | |
8.2.6.3. Participants
Table 8.12 describes the Flyweight participants in the words of Gamma et al. [Gamma95, p.198-199] and lists the corresponding implementations developed in the evaluation.
Table 8.12 — Flyweight participants | |||
Participant | Description | Implementation | |
Flyweight |
| Textual<T> Character | |
ConcreteFlyweight |
| Word, AbstractCharacter, Letter, Symbol, Whitespace | |
UnsharedConcreteFlyweight |
| Sentence | |
FlyweightFactory |
| CharacterFactory | |
Client |
| Main |
8.2.6.4. Implementation
Removing extrinsic state — Design choice. Here, java.util.Locale represents extrinsic state for sentence structures, because it rarely makes sense to store a locale with each character, word, or even sentence. Managing shared objects — The CharacterFactory class uses a java.util.HashMap<K,V> to store flyweights, which are created on demand only in a thread—safe manner. The java.lang.Object.hashCode() and java.lang.Object.equals(Object) methods makes it very easy to handle flyweights using the Java Collections framework. Purging of old flyweights is a design choice.
8.2.7. Proxy
Proxy is a Structural pattern with Object scope. The implementation is located in the dk.rode.thesis.proxy package.
8.2.7.1. Intent
Provide a surrogate placeholder for another object to control access to it [Gamma95, p.207].
8.2.7.2. Structure
Figure 8.12 illustrates the Proxy implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.12 — Proxy UML Class diagram | |
8.2.7.3. Participants
Table 8.13 describes the Proxy participants in the words of Gamma et al. [Gamma95, p.209-210] and lists the corresponding implementations developed in the evaluation.
Table 8.13 — Proxy participants | |||
Participant | Description | Implementation | |
Proxy |
| SynchronisedSequence<E>, NonResettableSequence<E>, ImmutableSequence<E>, java.lang.reflect.Proxy objects (SequenceProxyFactory, meta.reflect.proxy.ProxyFactory) | |
Subject |
| meta.model.Sequence<E> | |
RealSubject |
| Any Sequence<E> implementation |
8.2.7.4. Implementation
Overloading the member access operator in C++ — Java does not support operator overloading, but dynamic proxies can simulate the behaviour. The SequenceProxyFactory.getVirtualSequence(..) method returns a virtual (dynamic) proxy that will not create an actual Sequence<E> instance until a method declared in the Sequence<E> interface is invoked. Methods declared in other types can still be invoked, for example java.lang.Object.toString(). However, the actual class of the proxied sequence cannot be used for casts or instanceof tests using the proxy, since the class is java.lang.reflect.Proxy. This can be a severe limitation on dynamic proxy usage since the client will not (necessarily) know a proxy is accessed in place of the real object. As java.lang.Object.equals(Object), for example, utilises instanceof, this can be a problem for object comparison and collection usage. This also affects the Java Handle/Body idiom used to manage shared objects acquired from meta.reflect.proxy.ProxyFactory. Using doesNotUnderstand in Smalltalk — Not possible in Java 6, since the signature of a method to be invoked is determined at compile—time, while only the actual type of the (polymorphic) object is determined at runtime [Sierra06, p.111]. Reflection does not allow creation of new methods at runtime, so only compile—time known methods can be invoked. However, method names (strings) can be used to identify methods, but will require an elaborate "framework" to fetch methods, dispatch if found, and error handling if not found ("does not understand"). This principle is used in several dynamic proxy implementations. Proxy does not always have to know the type of the RealSubject — The virtual sequence described above uses type literals to supply the generic type to be created. The protection proxies defined operate on interfaces, for example ImmutableSequence<E> that makes any Sequence<E> type immutable.
8.3.1. Chain of Responsibility
Chain of Responsibility is a Behavioural pattern with Object scope. The implementation is located in the dk.rode.thesis.chainofresponsibility package.
8.3.1.1. Intent
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it [Gamma95, p.223].
8.3.1.2. Structure
Figure 8.13 illustrates the Chain of Responsibility implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.13 — Chain of Responsibility UML Class diagram | |
8.3.1.3. Participants
Table 8.14 describes the Chain of Responsibility participants in the words of Gamma et al. [Gamma95, p.225-226] and lists the corresponding implementations developed in the evaluation.
Table 8.14 — Chain of Responsibility participants | |||
Participant | Description | Implementation | |
Handler |
| Handler<R> | |
ConcreteHandler |
| CharacterHandler LetterHandler, LetterCaseHandler, SymbolHandler, WhitespaceHandler | |
Client |
| Main |
The implementation makes the chain explicit, represented by the HandlerChain<R> class. Ergo, the name and minimal description of the "pseudo—participants" as listed in table 8.15 below are not defined by Gamma et al.
Table 8.15 — Additional Chain of Responsibility entities | |||
Name | Description | Implementation | |
HandlerChain |
| HandlerChain<R> AbstractHandlerChain<R>, StandardHandlerChain<R>, WeakHandlerChain<R> | |
HandlerLink |
| HandlerLink<R> |
8.3.1.4. Implementation
Implementing the successor chain — The implementation differs from the canonical implementation by Gamma et al. in that it makes the chain explicit, represented by the HandlerChain<R> type. This means a concrete handler can only access its successor through the chain as it does not store a reference to it itself. This reduces coupling between handlers; handlers can participate in several chains; and allow a chain to be altered at any time by adding or removing handlers to and from it. Connecting successors — The HandlerLink<R> type represents a link to the next handler in the chain, if any. Representing requests — The type of request is specified using generics in the Handler<R> class, where R is the type of request. This is compile—time type—safe and flexible. Automatic forwarding in Smalltalk — Not supported in Java (but see section 8.2.7.4).
8.3.2. Command
Command is a Behavioural pattern with Object scope. The dk.rode.thesis.command package contains the implementation.
8.3.2.1. Intent
Encapsulate a request as an object, thereby letting you parameterise clients with different requests, queue or log requests, and support undoable operations [Gamma95, p.233].
8.3.2.2. Structure
Figure 8.14 illustrates the Command implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.14 — Command UML Class diagram | |
8.3.2.3. Participants
Table 8.16 describes the Command participants in the words of Gamma et al. [Gamma95, p.236-237] and lists the corresponding implementations developed in the evaluation.
Table 8.16 — Command participants | |||
Participant | Description | Implementation | |
Command |
| Command<E> SequenceCommand<E> | |
ConcreteCommand |
| CompositeCommand<E>, NextCommand<E>, ResetCommand<E>, ReverseCommand<E>, LogCommand<E>, NullCommand<E> | |
Client |
| Main | |
Invoker |
| CommandProcessor | |
Receiver |
| Any Sequence<E> implementation |
The implementation differs from the canonical implementation by Gamma et al. in that it uses a Command Processor to maintain, execute, and possibly undo commands, corresponding to a simple variant of the "POSA" Command Processor pattern [Buschmann96, p.277]. The processor thus functions as the Invoker participant, but it will be handed the commands to execute by the Client. Hence, the name and minimal description of the "pseudo—participant" as listed in table 8.17 below are not defined by Gamma et al., but by Buschmann et al. [Buschmann96, p.280].
Table 8.17 — Additional Command entities | |||
Name | Description | Implementation | |
CommandProcessor |
| CommandProcessor, CommandProcessingResult<E> |
The execution of a Command<E> may spawn new commands to be executed immediately by the CommandProcessor in a depth—first manner. As far as we know, this is a novel approach to command execution. This lessen need for macro commands considerably and allow more control of command execution as composite (macro) commands no longer handle the execution of contained commands. Undo of spawned commands is possible in several different ways.
8.3.2.4. Implementation
How intelligent should the command be? — Design choice. Supporting undo and redo — The SequenceCommand<E> class uses mementos for undo, while sub—classes also employ other means of undo as necessary. The CommandProcessor class implicit uses a history list in form of a collection of commands passed to it for execution. Avoiding error accumulation in the undo process — By using the CommandProcessor class, the history list, undo, and error handling is made explicit and hence not up to the individual command. Error handling supports spawned commands. The implementation throws an exception in case undo fails, but different error handling strategies can be applied because of centralised control. Using C++ templates — This corresponds to using generics with an upper bound as described in section 8.1.3.4.
8.3.3. Interpreter
Interpreter is a Behavioural pattern with Class scope. The dk.rode.thesis.interpreter package contains the implementation.
8.3.3.1. Intent
Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language [Gamma95, p.243].
8.3.3.2. Structure
Figure 8.15 illustrates the Interpreter implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.15 — Interpreter UML Class diagram | |
8.3.3.3. Participants
Table 8.18 describes the Interpreter participants in the words of Gamma et al. [Gamma95, p.245-246] and lists the corresponding implementations developed in the evaluation.
Table 8.18 — Interpreter participants | |||
Participant | Description | Implementation | |
AbstractExpression |
| Expression<E> TypedExpression<E>, InitialisableExpression<E> | |
TerminalExpression |
| TerminalExpression<E>, SequenceExpression<T,E>, CurrentExpression<E>, NextExpression<E>, ResetExpression<E>, ReverseExpression<E>, SetExpression<E> | |
NonTerminalExpression |
| NonTerminalExpression<E>, BinaryExpression<T,E>, AndExpression, AssignmentExpression<E>, BreakExpression<E>, CompareExpression<E>, ConditionalExpression<E>, ConstantExpression<E>, EqualExpression, FlowExpression<E>, NotExpression, OrExpression, VariableExpression<E> | |
Context |
| Context | |
Client |
| Main |
Unlike Gamma et al., the implementation makes the interpreter explicit. This allows for much better error handling and transfer of evaluation control. Hence, the name and minimal description of the "pseudo—participant" as listed in table 8.19 below are not defined by Gamma et al.
Table 8.19 — Additional Interpreter entities | |||
Name | Description | Implementation | |
Interpreter |
| Interpreter<T> |
8.3.3.4. Implementation
Creating the abstract syntax tree — Design choice. Here, the Builder implementation is used to build expressions that can be assembled. Defining the interpret operation — The Expression<E> type defines the evaluate(Context) method, but the Interpreter<T> class defines the interpret method, performs error handling, and flow control. Sharing terminal symbols with the Flyweight pattern — Design choice.
8.3.4. Iterator
Iterator is a Behavioural pattern with Object scope. The dk.rode.thesis.iterator package contains the implementation.
8.3.4.1. Intent
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation [Gamma95, p.257].
8.3.4.2. Structure
Figure 8.16 illustrates the Iterator implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.16 — Iterator UML Class diagram | |
8.3.4.3. Participants
Table 8.20 describes the Iterator participants in the words of Gamma et al. [Gamma95, p.259] and lists the corresponding implementations developed in the evaluation.
Table 8.20 — Iterator participants | |||
Participant | Description | Implementation | |
Iterator |
| java.util.Iterator<E> (external), ProcessableSequence<E>, ValueProcessor<E> (internal) | |
ConcreteIterator |
| SequenceIterator<E> (external), LoggingValueProcessor<E> (internal) | |
Aggregate |
| java.lang.Iterable<T> (external) | |
ConcreteAggregate |
| IterableSequence<E> (external) |
No classes have been defined to represent Aggregate and ConcreteAggregate for internal iterators. Java 6 defines no standard interface for internal iterators. The creation is left at the discretion of the context using internal iterators.
8.3.4.4. Implementation
Java has built—in API and language support for the Iterator pattern. Who controls the iteration? — The SequenceIterator<E> class represents an external iterator, while the ProcessableSequence<E> represents an internal iterator. Who defines the traversal algorithm? — The composite.CompositeStrategy utilises package private access to ensure encapsulation and information hiding is not violated when the traversal algorithm is external. How robust is the iterator? — Design choice. Standard iterators in Java are fail—fast, and fail immediately in case of concurrent modification. Additional Iterator operations — Design choice. Using polymorphic iterators in C++ — java.lang.Iterable<T> defines a factory method to return a java.util.Iterator<E> instance and the usage is illustrated in the IterableSequence<E> class. Iterators may have privileged access — Illustrated in the meta.reflect.CallerClass.CallerIterator<C> inner class. Iterators for composites — The composite.CompositeStrategy determines how the composite structure should be traversed. Null iterators — Trivial, though note that making a null iterator for the meta.model.Sequence<E> type is not possible, because sequence semantics require that a sequence always have at least a single value.
8.3.5. Mediator
Mediator is a Behavioural pattern with Object scope. This pattern is not implemented due to a lack of applicability because of its abstraction and granularity level. It offers no new information compared to the rest of the "Gang of Four" patterns with regards to language functionality.
8.3.5.1. Intent
Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently [Gamma95, p.273].
8.3.5.2. Participants
Table 8.21 describes the Mediator participants in the words of Gamma et al. [Gamma95, p.277].
Table 8.21 — Mediator participants | |||
Participant | Description | Implementation | |
Mediator |
| - | |
ConcreteMediator |
| - | |
Colleague Class |
| - |
8.3.5.3. Implementation
As stated, the Mediator pattern has not been implemented because it only addresses general design issues. We still address the Implementation element, since it only discuss two items. Omitting the abstract Mediator class — Design choice. Colleague/Mediator communication — Design choice. Using the Observer pattern is possible as the means to communicate, for example the stand—alone observer.ObserverManager class that could handle different kind of colleagues without requiring them to implement a common super type. Passing itself as an argument is a common approach with trivial implementation. Also supported by ObserverManager.
8.3.6. Memento
Memento is Behavioural with Object scope. The implementation is located in the dk.rode.thesis.memento package.
8.3.6.1. Intent
Without violating encapsulation, capture and externalise an objects internal state so that the object can be restored to this state later [Gamma95, p.283].
Note that according to our separation of encapsulation and information hiding into two distinct concepts as described in section 2.1.1, encapsulation as described above refers to both concepts. Encapsulation must not be broken in the sense that the state must still be localised in the object, which must be hidden to shield it from unwanted access.
8.3.6.2. Structure
Figure 8.17 illustrates the Memento implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.17 — Memento UML Class diagram | |
8.3.6.3. Participants
Table 8.22 describes the Memento participants in the words of Gamma et al. [Gamma95, p.285] and lists the corresponding implementations developed in the evaluation.
Table 8.22 — Memento participants | |||
Participant | Description | Implementation | |
Memento |
| SequenceMemento<E>, GuardedSequenceMemento<E> | |
Originator |
| Any MemorizableSequence<E> implementation like RangeSequence and MemorizableEnglishAlphabetSequence | |
Caretaker |
| Main |
8.3.6.4. Implementation
Language support — Java does not support narrow (public) and wide (private) interface functionality because all methods declared in an interface must be public. A class may define and implement private methods, but they are inaccessible to other types. The GuardedSequenceMemento<E> class illustrates how private methods par design have to be made public for compile—time safety, but are guarded at runtime to ensure that only legal callers are allowed (see section 7.1.4.1). Storing incremental changes — Design choice. The command.SequenceCommand<E> class use mementos to store the complete state.
8.3.7. Observer
Observer is Behavioural with Object scope. The implementation is located in the dk.rode.thesis.observer package.
8.3.7.1. Intent
Define a one—to—many dependency between objects so that when one object changes state, all dependants are notified and updated automatically [Gamma95, p.293].
8.3.7.2. Structure
Figure 8.18 illustrates the Observer implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.18 — Observer UML Class diagram | |
8.3.7.3. Participants
Table 8.23 describes the Observer participants in the words of Gamma et al. [Gamma95, p.295] and lists the corresponding implementations developed in the evaluation.
Table 8.23 — Observer participants | |||
Participant | Description | Implementation | |
Subject |
| Observable<O>, AspectObservable<O,A> ObservableSequence<O,A,E>, AspectObservableSequence<O,A,E> SequenceObserversSequence<E,A>, AnnotatedObserversSequence<E> | |
Observer |
| SequenceObserver<A> and any java.lang.Object annotated with the meta.reflect.@Executor annotation when used with ObserverManager or a sub—class of AnnotatedObserversSequence<E> | |
ConcreteSubject |
| ObserverManager, DateSequence SequenceObserversSequenceDecorator<E,A>, AnnotatedObserversSequenceDecorator<E> | |
ConcreteObserver |
| CorrelatedSequenceObserver, PrintSequenceObserver, ProbeSequenceObserver, BirthdayRegistry |
8.3.7.4. Implementation
Mapping subjects to their observers — java.util.Map<K,V> is used to store observers for fast lookup. Observing more than one subject — The stand—alone ObserverManager class can manage and notify observers, typically used as a delegate by another object in a composition. It can store observers with different types, but using the same signature to notify the observers. Notification methods are specified via annotations, Its notifyObservers(java.lang.Object...) method uses varargs and it is up to the client to specify the signature of the actual observer notification methods and to supply the proper arguments. Hence, whether or not the subject is passed to the notification methods is a design choice based on the signature of the notification methods used. The AnnotatedObserversSequence<E> uses ObserverManager as a delegate and passes itself as the first argument to notifyObservers(Object...) when notification is performed. Observers can thus observe several subjects. Who triggers the update? — Design choice. ObservableSequence<O,A,E> implementations issue notifications when their internal state changes, for example on invocation on next() and reset(). Dangling references to deleted subjects — Subjects storing observers as hard references will prevent observers from being garbage collected. On the other hand, the ObserverManager class stores observers as weak references and if there are no other references to a given observer, it will be garbage collected. The manager handles this and purges weak references transparently when the observer has been garbage collected. Making sure Subject state is self—consistent before notification — Template Method is used in the SequenceObserversSequence<E,A> and AnnotatedObserversSequence<E> classes to handle the notification in a state—consistent manner. Avoiding observer—specific update protocols — The ObserverManager component can be used for both push and pull semantics because the type of observers and notification methods are configurable. Specifying modifications of interest explicitly — Implementations of AspectObservable<O,A> allow observers of type O to subscribe to specific aspects of type A. Encapsulating complex update semantics — The ObserverManager class can be considered a Change Manager. Sub—classes could simply override the notification method for different notification strategies.
8.3.8. State
State is a Behavioural pattern with Object scope. The dk.rode.thesis.state package contains the implementation.
8.3.8.1. Intent
Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class [Gamma95, p.305].
8.3.8.2. Structure
Figure 8.19 illustrates the State implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.19 — State UML Class diagram | |
8.3.8.3. Participants
Table 8.24 describes the State participants in the words of Gamma et al. [Gamma95, p.306-307] and lists the corresponding implementations developed in the evaluation.
Table 8.24 — State participants | |||
Participant | Description | Implementation | |
Context |
| StateableSequence<E>, AbstractStateableSequence<E>, ReversiblePrimeSequence StepSequence | |
State |
| FunctionalState<E> StepSequenceImpl | |
ConcreteState |
| Each constant in the ReversiblePrimeSequence.PrimeState enumeration EvenSequence and OddSequence |
8.3.8.4. Implementation
Who defines the state transitions — Design choice. Transitions are primarily handled by concrete states, such as the EvenSequence class and the ReversiblePrimeSequence.PrimeState constants, but also by ReversiblePrimeSequence when reversed. A table—based alternative — Design choice. Trivial, not implemented. Creating and destroying State objects — ReversiblePrimeSequence.PrimeState is an enumeration and all states are thus known beforehand. The states are stateless, small, and cheap to create. Creation (and destruction) is handled automatically and singleton behaviour for each state is guaranteed by the compiler. EvenSequence and OddSequence states are created on demand and garbage collected when no longer used. Using dynamic inheritance — Not supported directly by Java, but dynamic proxies can simulate the behaviour. The invocation handler used by the proxy can change the target object of a given method (signature) to supply different implementations at runtime. This is illustrated in the StepSequence dynamic proxy that uses instances of the EvenSequence and OddSequence classes as two different implementations.
8.3.9. Strategy
Strategy is a Behavioural pattern with Object scope. Also known as Policy. The implementation is located in the dk.rode.thesis.strategy package.
8.3.9.1. Intent
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it [Gamma95, p.315].
8.3.9.2. Structure
Figure 8.20 illustrates the Strategy implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.20 — Strategy UML Class diagram | |
8.3.9.3. Participants
Table 8.25 describes the Strategy participants in the words of Gamma et al. [Gamma95, p.317] and lists the corresponding implementations developed in the evaluation.
Table 8.25 — Strategy participants | |||
Participant | Description | Implementation | |
Strategy |
| StringablePolicy<T> | |
ConcreteStrategy |
| Each SequencePolicy enumeration constant, and each ObjectPolicy enumeration constant | |
Context |
| Any Stringable<T> implementation, including sequences as they implement the interface |
8.3.9.4. Implementation
Defining the Strategy and Context interfaces — Design choice. The Stringable<T> interface pass itself to a concrete strategy of type StringablePolicy<T>, i.e. delegation is used. Strategies as template parameters — Generics with an upper bound can be used in place of templates, where the upper bound identifies the strategy functionality. This is analogous to the abstractfactory.PrototypicalRegistry functionality that uses an upper bound of StrictCopyable<?> to ensure that the actual type has a copy() method. Making Strategy objects optional — Trivial design choice. This is illustrated in the Stringable<T> interface that will apply a default strategy if none is supplied.
8.3.10. Template Method
Template Method is a Behavioural pattern with Class scope. The dk.rode.thesis.templatemethod package contains the implementation.
8.3.10.1. Intent
Define the skeleton of an algorithm in an operation, deferring some steps to sub—classes. Template Method lets sub—classes redefine certain steps of an algorithm without changing the algorithm's structure [Gamma95, p.325].
8.3.10.2. Structure
Figure 8.21 illustrates the Template Method implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Figure 8.21 — Template Method UML Class diagram | |
8.3.10.3. Participants
Table 8.26 describes the Template Method participants in the words of Gamma et al. [Gamma95, p.327] and lists the corresponding implementations developed in the evaluation.
Table 8.26 — Template Method participants | |||
Participant | Description | Implementation | |
AbstractClass |
| SequenceTemplate<K,E> | |
ConcreteClass |
| ZipSequence, FileSequence, NegativeSequence |
8.3.10.4. Implementation
Using C++ access control — Primitive operations are declared as protected methods in the SequenceTemplate<K,E> abstract class. All sub—classes and classes in the same package can thus execute the primitive operations. Primitive operations that must be implemented are declared abstract, while the template methods have to be declared final to ensure that they cannot be overridden. Unlike C++, primitive operations overridden by a sub—class can be called in the (super) constructor. This is illustrated in SequenceTemplate<K,E>. A potential issue is that Java does not support multiple inheritance. Once the abstract class is inherited, no other classes can be inherited. An alternative is to use composition and interface implementation as illustrated in the builder.TypedExpressionBuilder<E> class; Builder often rely on sub—classing like Template Method. Minimizing primitive operations — This is a design choice. Naming conventions — Design choice. SequenceTemplate<K,E> utilises meaningful method name prefixes to identify primitive operations. Potentially important for reflective invocation.
8.3.11. Visitor
Visitor is Behavioural with Object scope. The implementation is located in the dk.rode.thesis.visitor package.
8.3.11.1. Intent
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates [Gamma95, p.331].
8.3.11.2. Structure
Figure 8.22 illustrates the Visitor implementation as an UML Class diagram, where the pattern participants can also be identified. The pattern participants are described in the next section.
Notice that there are two separate hierarchies of visitors: sequence value visitors and type visitors. The first performs visitation based on the type of values delivered by a sequence, while the latter performs visitation based on actual sequence type.
Figure 8.22 — Visitor UML Class diagram | |
8.3.11.3. Participants
Table 8.27 describes the Visitor participants in the words of Gamma et al. [Gamma95, p.334-335] and lists the corresponding implementations developed in the evaluation.
Table 8.27 — Visitor participants | |||
Participant | Description | Implementation | |
Visitor |
| SequenceVisitor<P> SequenceTypeVisitor<P>, SequenceValueVisitor<P> | |
ConcreteVisitor |
| TypeVisitor, CountingVisitor, LoggingVisitor | |
Element |
| TypeVisitableSequence<E>, ValueVisitableSequence<E> AbstractVisitableSequence<E> | |
ConcreteElement |
| VisitableCompositeSequence, VisitableLongSequence, VisitableRandomSequence, VisitableReversiblePrimeSequence StringValuedVisitableSequence<E>, IntegerValuedVisitableSequence, DateValuedVisitableSequence, ReflectiveVisitableSequence<E> | |
ObjectStructure |
| SequenceTypeScanner, SequenceValueScanner SimpleScanner |
8.3.11.4. Implementation
The SequenceValueVisitor<P> cannot overload visitation methods because it performs visitation based on type parameters that are erased at runtime. Double—dispatch — As C++, Java only supports single—dispatch, so Visitor is indeed relevant. The ReflectiveVisitableSequence<E> is a decorator that uses reflection and naming conventions to avoid a static binding of the actual visitation method. Annotations can also be used as illustrated in observer.ObserverManager that uses annotations to determine notification methods. Who is responsible for traversing the object structure? — The traversal of the composite sequence structure is made explicit by use of scanners, e.g. the SequenceTypeScanner and SequenceValueScanner types. Scanners represent the object structure, but only operate on it. Composite sequences do not traverse their children, this is handled by scanners as well. This allows scanners full control over the traversal strategy, for example depth—first or breath—first.
8.4. Summary
Below, we list and summarise the key issues from the detailed evaluation of the "Gang of Four" patterns:
- The evaluation shows that practically all functionality described in the Implementation and Sample Code elements in the "Gang of Four" patterns can be implemented in Java 6, not just the canonical implementations but all functionality—related issues discussed by Gamma et al. in these pattern elements.
- The implementations express "Best Practices" from both Java 6 and design patterns epitomised by the "Gang of Four" patterns.
The pattern implementations illustrate that only Adapter with Class scope cannot be done in Java 6. All other pattern functionality can be implemented or simulated, but may require more work. Adapter with Class scope fails because the pattern abstraction and functionality, and not merely implementation issues, is targeted at languages supporting multiple inheritance. In this respect, we conclude that Adapter with Class scope is in fact a C++ idiom representing the general Adapter abstraction. Adapter with Object scope is easily implemented in Java 6, and dynamic proxies can furthermore be used to simulate multiple inheritance. However, in general, there are some problems related to the use of dynamic proxies that may influence pattern behaviour, such as testing for object equality.
The implementations express the themes and concepts described by Gamma et al. in a realistic manner, but also the "Best Practices" offered by Bloch concerning Java [Bloch01]. All implementation issues discussed by Gamma et al. in the Implementation and Sample Code elements in the "Gang of Four" patterns are expressed or simulated in the implementations.