loop: LDA data,X
STA $0400,X
INX
CPX #$0A
BNE loop
RTS
data: .BYTE $03,$36,$34,$20,$34,$05,$16,$05,$12,$21
— Gunni Rode
This chapter presents the practical details regarding the pattern implementations. We present the environment set—up used for the evaluation, including precise Java version and IDE. Since UML cannot describe all Java features, we explain the UML extensions defined and used during the evaluation to aid the construction of UML Class diagrams. To simulate a larger "application" than standalone pattern implementations can achieve, we present the core model classes used directly or indirectly in all pattern implementations. Finally, we describe how the pattern implementations can be executed and tested.
6.1. Software
JDK 1.6.0_01 is used with compiler compliance to version 6. The JDK is available for download at http://java.sun.com/javase/downloads/index.jsp. JavaDoc is used to document the source code and is bundled with the JDK. The standard doclet is used with a compliance level to version 6.
Eclipse 3.3 (Europa) is used as the main IDE. It is available at http://www.eclipse.org/downloads. NetBeans 5.5.1 from Sun is used as the secondary IDE since Eclipse utilises its own compiler. To ensure compatibility with the standard compiler, NetBeans is used to verify compilation - the compilers do not behave exactly alike. Known issues are documented with //ISSUE:. NetBeans is available for download at http://www.netbeans.org. There is a single compiler error in NetBeans in the bridge.SequenceAbstraction<E> class, line 305, but it does not concern core pattern functionality. In our opinion, it is a compiler bug (well, at least in one of the compilers...). There are no problems in Eclipse.
A deliberate choice is that no plug—ins for Eclipse or NetBeans are required, not even JUnit. The OS used during development is Microsoft XP Professional, SP2.
6.2. Modelling
Each pattern implementation is only illustrated with an UML Class diagram, similar to the Class diagram shown in figure 6.1. Standard UML notations are not described here, but UML cannot describe all Java features, such as final methods, annotations, or genetic bounds. Fortunately, it is extensible. Additional data types, stereotypes, and attributes are thus defined and used as explained below in table 6.1.
Packages are rarely depicted. If so, it is only to illustrate a clear separation between patterns and/or classes. Types include attribute and operation (constructor and method) information as deemed necessary. Two dots (..) indicate additional attributes/operations not depicted. Open—ended sub—classes are generally not depicted, but implied. All Class diagrams use Java types for clarity, pictured with their fully qualified generic name such as java.util.List<E>. All types defined in this thesis are presented by their simple generic name, such as Sequence<E> for dk.rode.thesis.meta.model.Sequence<E>. Inner classes are qualified by their enclosing class, for example Sequence.State. Parameterised type realisations are depicted with rounded corners (bind relations), which is a deviation from the UML standard. Realisations from type parameter E to E are not illustrated, only when bound to a concrete type such as java.lang.Integer, or to a type parameter with a different name, e.g. E to T. Bounds on type parameters use Java syntax, like E extends T, E super S, or even wild—cards like ? super E. All this is illustrated in figure 6.1. Comments are light grey.
The UML Class diagrams identify the pattern participants in a manner similar to a format suggested by Vlissides, one of the "Gang of Four" authors. Here, a participant is identified by a dark blue rectangle containing the participant name in the upper left corner of the type.
6.3. Design
All pattern implementations in this evaluation relate to a few common model classes defined in the dk.rode.thesis.meta.model package. This is part of the deliberate design choice to simulate larger and more complex applications than could be achieved by disjoint stand—alone pattern implementations, but also to keep the project within reason, time and development wise. Individual implementations can thus be used in other pattern implementations as well, expressing many of the pattern relationships described by Gamma et al. The primary type is the Sequence<E> interface, which represents a sequence that will deliver the next, or current, value in given sequence on demand, such as for example a Fibonacci sequence or a sequence delivering the names of the Simpsons family members. Sequences are more than mere iterators; they are value centric and have a number of interesting properties that makes them useful in an evaluation such as this. Sequences always have a lower bound, i.e. the initial sequence value, and may have an upper bound, limiting the number of possible values it can deliver. If a sequence is bounded and deliver consistent values, it will restart when the upper bound is reached on an invocation of next(), i.e. the same values will be delivered again, in order. The sequence values of bounded, consistent sequences are thus deterministic: two instances of the same sequence type initialised identically will return the same sequence values if utilised in the same manner. Sequences may also deliver unique values until reset or restarted. Sequences can be reset explicitly, which will cause the sequence to restart if it is consistent. The complete Sequence<E> interface is illustrated in figure 6.1, including closely related types, but please refer to the JavaDoc for an in—depth description.
A given pattern implementation will either use or implement sequence functionality, but the Sequence<E> type is often merely a catalyst to make the implementation and evaluation concrete. The Abstract Factory implementation, for example, provides a reusable factory that can create any type of product, but the products created are sequence related. Other implementations are more entangled with sequence functionality. The implementation of the State pattern is a sequence implementation delivering prime numbers, where each concrete state represents internal sequence functionality, such as calculating prime numbers, delivering the next prime number, restarting the sequence, etc. Usage includes the Adapter implementation, which adapts the Sequence<E> interface to the java.util.Iterator<E> interface via composition, and the Interpreter implementation, which evaluates expressions that directly or indirectly manipulate sequences.
Arguably, the choice to centre all pattern implementations on a few core model types may seem contrived. There is no guarantee that "one—size—fits—all", especially considering the scope of this thesis. The evaluation of design patterns from a practical point of view requires a real context to be truly educational. Through real, practical application of a pattern using a given language will the connection between the two become apparent. Design patterns should be applied only where relevant. A design forcing the use of certain design patterns is not only contrived, it goes against the very idea of design patterns. An evaluation like this one can only try to imitate a real context. It has no choice but to implement each pattern within that context as it is the very purpose of the evaluation. On the other hand, sequence functionality as described above is generic enough to allow for many different applications of it, which we think the evaluation demonstrates. It helps convey the idea of an overall "application". At first glance, a general impression of the design and implementation as whole could be that it suffers from featuritis, but this is in fact not the case. On the contrary, reusing common components such as sequences allow individual pattern application to become focused, added only what is needed while still participating in non—trivial overall implementations. Accordingly, several pattern implementations define sub—interfaces of Sequence<E> to express the required functionality, and such types represent the focus of the given pattern implementation. Examples include Composite, Observer, and Visitor that defines the composite.CompositeSequence<E>, observer.ObservableSequence<O,A,E>, and visitor.TypeVisitableSequence<E> interfaces, respectively. The actual implementations need only be concerned with specific pattern functionality as general sequence functionality can be reused or inherited.
Figure 6.1 — Primary model classes | |
6.4. Source Code
Table 6.2 lists the packages containing the developed source code. Approximately 300+ Java files have been developed, yielding approximately 400+ class files (including inner classes and enumeration constants). The source code can be downloaded from the thesis website at http://www.rode.dk/thesis.
All types are fully documented using JavaDoc, including packages. The reader of this thesis is expected to browse the generated JavaDoc to get a better understanding of the different implementations. The primary implementation of each pattern is implemented in each own aptly named package, e.g. dk.rode.thesis.abstractfactory for Abstract Factory. A given pattern implementation may naturally be utilised by other patterns, and additional applications of a given pattern may be present in the source code as well, for example anonymous classes used as on—the—fly adapters. Several Meta packages and classes have been developed to aid the individual pattern implementations. An example is the dk.rode.thesis.meta.model package as described in the previous section, or the dk.rode.thesis.meta.reflect package that supplies the core reflection functionality used in different pattern implementations. They are listed in grey cells in table 6.2 below. Refactoring common code into Meta classes is also a good design choice in compliance with traditional OO concepts instead of implementing everything from scratch in each pattern implementation. It is the contents of each "pattern package" that are evaluated in chapter 8, but Meta class functionality will be included if it is essential for the pattern functionality.
Each pattern package contains a doc-files folder containing an UML Class diagram for the implementation. This ensures that the diagram is included in the generated JavaDoc. The diagrams are included in this thesis as well, in the relevant evaluation sections.
In the evaluation, full package names will rarely be used for the developed source code. Package prefix dk.rode.thesis is always implied if not already included in a given type name. Once a given type or package has been referenced in a context, e.g. section or paragraph, the remaining package information will be ignored as well. Example: if the full type name is dk.rode.thesis.composite.CompositeSequence<E>, composite.CompositeSequence<E> will suffice, and additional references in the same context will thereafter simply reference CompositeSequence<E>. Java types are fully qualified, or go by their simple name when referenced again within the same context, as for example java.util.NavigableMap<K,V> and NavigableMap<K,V>.
6.5. Testing
Each pattern package includes a Main class that will execute the tests devised to illustrate the developed pattern functionality, e.g. dk.rode.thesis.abstractfactory.Main. The tests are not meant as a replacement for JUnit testing, but to illustrate pattern functionality. They can each be run directly, but the dk.rode.thesis.meta.test package furthermore includes two separate test classes, namely AllTests and IntegrityTests. The first runs all individual pattern tests in alphabetical order, while the latter perform integrity tests on all accessible dk.rode.thesis.meta.model.Sequence<E> implementations defined in the individual pattern implementations. Note that certain test files are required to run the Template Method tests and that Bridge and Memento, as well as the logger, will write to disk.
To record the outcome of the tests, two types of logs exist: a global log and logs associated with a specific class. The output is generally verbose as all objects in the evaluation implement meaningful toString() representations as recommended by Bloch [Bloch01, p.42-44]. It is possible to control the log level explicitly in the individual tests by altering the source code, of course, but it is easier to supply a proper boolean value for the -log argument to each test class, indicating whether or not logs associated with individual classes should be activated or not, e.g. java Main -log true. For additional verbose logging, the -log.verbose parameter can be used in a similar fashion. Notice there is no = operator between argument keys and associated values. The tests are designed so the global log should suffice to convey the intent.
The logs will output to either System.out or to a file. This is determined by the system property dk.rode.thesis.log. A string value of "file", excluding quotation marks, will log to a file in the relative directory log; all other values will cause logging to System.out. File logs will append to existing logs. System properties are supplied with the -D option during execution, as in java -Ddk.rode.thesis.log=file Main -log true. Notice the use of = unlike normal supplied arguments.
6.6. Summary
We have implemented the "Gang of Four" patterns in Java 6, fully documented with JavaDoc. The source code expresses the "Best Practices" described by Bloch [Bloch01] whenever possible. Each specific pattern implementation has a dedicated package, but may be used in other pattern implementations as well. All implementations basically operate on the same core model classes to simulate "application" usage. Additional use of the "Gang of Four" patterns is applied where warranted, for example in Meta classes or as part of another pattern implementation. Test classes have been developed to illustrate pattern usage.