— Aristophanes
The detailed and comparative evaluations disclose much information concerning individual and collective behaviour, including summaries and in—part conclusions where appropriate. This chapter comments on the evaluation as a whole. We determine the compliance level of the developed pattern implementations compared not only to the functionality examined, but also to the concepts and themes described by Gamma et al. Based on the compliance level, we determine how well Java 6 suits the "Gang of Four" patterns from a practical perspective. Several pattern implementations are interesting enough to be classified as high—lights of the evaluation. These pattern implementations are briefly described and their functionality, applied techniques, and used features are put into perspective. Finally, for others to examine and judge the evaluation and its results, the evaluation approach must not only be meaningful, but understood. We therefore present an evaluation of the evaluation approach, and thus of the evaluation itself, to put the results into perspective.
9.1. Implementation Compliance
The evaluation goals from section 5.3 have all been achieved. All "Gang of Four" patterns except Mediator have been implemented, while all have been evaluated. All representative pattern functionality described in the Implementation and Sample code elements in the "Gang of Four" pattern descriptions have been addressed. Only Adapter with Class scope fails because the core pattern abstraction and functionality is directly targeted at languages supporting multiple inheritance. All other pattern functionality can be implemented or simulated.
Several patterns include different variants, some close to the canonical implementations offered by Gamma et al., while many utilise Java mechanisms alien to C++, such as annotations, dynamic proxies, bounded generics, etc. Even though the evaluation quite frequently utilises advanced features, the implementations fundamentally rely on OO concepts like types, classes, objects, inheritance, polymorphism, etc., as described by Gamma et al. The specific features used are summarised in section 7.1. Furthermore, the developed Meta classes also make good use of applying the "Gang of Four" patterns internally. Regardless of mechanisms used, the participants and their functionality as described by Gamma et al. are logically named and clearly identifiable in the source code. Even more so, the individual participants for each pattern is annotated with annotations describing their name and classification down to a level of a specific class, interface, method, or even field. The information is also present in the generated JavaDoc, aiding the identification of participants even further.
The implementations not only express the themes and concepts described by Gamma et al., but also the "Best Practices" described by Bloch for Java applications. This augments the quality of the pattern implementations. We believe that many of the classes produced in the evaluation could be used "as is" in real—life applications. However, the Java 6 implementations are generally more dynamic compared to the canonical implementations. This may conflict with the warning set forth by Gamma et al., which states that highly parameterised, dynamic software may be harder to understand than static software.
9.2. Language Features
Java's core language features promote robustness, pattern intent, and reusability, and form the base of all the pattern implementations. The evaluation confirms the observations regarding dynamic features and reflection made by Norvig and Sullivan examined in chapter 4. We do not claim that Java's dynamic and reflective capabilities make the "Gang of Four" pattern implementations easier, but combined with core features they certainly allow for alternative and more flexible implementations compared to the canonical examples by Gamma et al. Creational and Behavioural patterns especially benefit from such usage. Annotations deserve special mention because they bridge the gap between compile—time and runtime. They can be used to express pattern intent at compile—time as well as runtime, and opens up for many new and interesting ways to express pattern functionality.
The language features examined are all present in Java 5 as well. With minor source code modifications, the code compiles with JDK 1.5.0_12 without errors14. Conclusions drawn are therefore also applicable to Java 5.
9.3. High—Lights
Table 7.1 emphasises what we consider the representative high—lights discovered during the evaluation. The high—lights are illustrated with dark—blue background, for example the Abstract Factory x Generics entry. The high—lights represent the pattern functionality expressed using the features. They are representative because the features may be applied in other patterns as well, and hence we put the pattern and feature use in perspective here. The high—lighted features are typically dependent on each other, for example the use of generics, type literals, and constructors for Abstract Factory. Below, the pattern x feature combinations are summarised in no particular order, and the feature dependencies explained. We believe the most realistic use of high—lights are Abstract Factory, Factory Method, Observer, and Singleton implementations. Memento, Proxy, and State may be too exotic for general use, but still useful in specialised cases. The high—lights are closely related to several of the feature observations described in section 7.1.4.
9.3.1. Abstract Factory and Factory Method
The Factory Method implementation shows that it is possible to create a reusable factory that can create precise generic types, T, in a type—safe manner based on type literals. The same principle applies to Abstract Factory. The reusable factory component is the factorymethod.Factory<T> abstract class. Sub—classing is required to acquire the type of T and to specify the constructor to create new instances reflectively. Reflection is only used to determine the type of T and to create new instances of T, where after T instances are accessed normally. This corresponds to minimal reflective usage as described by Bloch [Bloch01, p.159]. The factory is a simple generalisation of the meta.reflect.InstantiableTypeLiteral<T> class. Type literals show how patterns can manipulate generic types in a type—safe fashion. This is important as the evaluation indicates a prolific use of generics. Section 7.1.2.2 explains the precise usage of type literals, and the Factory<T> class is illustrated in listing 7.18 as well as listing 7.19. Section 8.1.3 provides the detailed description of the Factory Method implementation, while section 8.1.1 provides the description for Abstract Factory. Type literals are also used by Prototype, Proxy, Singleton, and Visitor.
9.3.1.1. Perspective
Central to many designs are Creational patterns such as Abstract Factory and Factory Method. They provide the core business objects the rest of the application depends on; load classes on demand; create other types of factories and interrelated hierarchies; and help promote object composition over class—based inheritance [Gamma95, p.81]. Creational patterns provide flexibility, but often at the expense of more verbose code and little reuse [Gamma95, p.85]. In Java, creating generic product types can also be tricky as this evaluation clearly demonstrates. A reusable component such as Factory<T> can remedy many of the pitfalls associated with Creational pattern usage. Its intent is also clear by giving it a meaningful, but general, name. While the component is open for sub—classing for specialised behaviour, e.g. caching, it can be used "as is" for any (non—abstract) product type with an applicable constructor, perhaps as a component in another pattern. However, an exact type must be known at compile—time so it cannot be used when the type is determined at runtime. Still, the Singleton implementation demonstrates how dynamic class loading can be used with type literals to enforce generic bounds in certain situations. These issues certainly have an impact on the design and development process because the designer can now utilise the knowledge and component(s). We believe the functionality provided by Factory<T> is needed and general enough to warrant an API inclusion in the long run, but more research and testing is required.
9.3.2. Memento
As the evaluation shows, interface usage is practically unavoidable when applying the patterns using Java, but interface behaviour must be public. As discovered and explained in section 7.1.4.1, the Memento implementation illustrates how the use of friends and interfaces with narrow (public) and wide (private) methods in the canonical C++ implementations can be transformed to Java 6 in a type—safe manner using guarded types. Iterator and State also use the same C++ features. A Java solution is to declare the otherwise private (wide) methods as part of the interface. The methods thus become public, but they must then guard access to themselves at runtime. Exception stack traces combined with class literals identify the caller of a given context. Appropriate action can then be taken in case the caller is illegal. The caller can be identified at the level of a class, static initialiser block, constructor, or method. This is essentially an imitation of friends in C++. The cost is potentially error prone runtime access checks enforced by the developer versus compiler guaranteed static checks in C++. Callers are represented by the meta.reflect.CallerClass and meta.reflect.Caller types and section 7.1.2.1 explains their usage. Memento usage is illustrated in listing 7.16 and 7.26. Section 8.3.6 provides the detailed description of the Memento implementation. Caller identification is also used in Singleton and Bridge, but could easily have been applied in other patterns as well.
9.3.2.1. Perspective
Interface implementation is paramount in any Java program, not just in pattern implementations. The demand that all interface methods are public can have serious consequences for any design. It forces verbose types in the sense that the original type will have to implement all interface functionality publicly. It is not uncommon that necessity requires a type to implement an interface expressing functionality that is private by nature. A common way to circumvent this is to use private inner classes to implement, or adapt, functionality as is illustrated in for example the Observer implementation. However, this does not work when communication is required between two different objects as is the case with Memento. Packages can solve the problem locally by declaring otherwise private methods package private or even protected, thereby making all other classes in the same package "friends". It is plausible this is sufficient for pattern usage if the pattern is applied in an Informal way, within the same package and not using interfaces to describe the functionality. If the objects span different packages, however, a public type must be used to establish the contract between the objects to ensure type safety. Memento shows how type safety can be combined with runtime access checks to protect its (otherwise) private state.
The solution promotes type safety for applicable patterns, but may confuse developers because the guarded functionality is callable at development—time. Familiar pattern interfaces may suddenly change, but because of the prolific use of interfaces in Java, the functionality may actually already be expressed publicly. This is the case with the State implementation in this evaluation. The only way to inform users of the intent is to include the information in the documentation, JavaDoc or otherwise. This is not unheard of, however, keeping the java.lang.UnsupportedOperationException from the Java Collections Framework in mind. While the principles utilised are sound and directly available in Java, we doubt its usage in real—life systems unless backed by a major player, e.g. Sun. There are too many unknowns and uncertainties connected with its usage, and acquisition of stack traces is not exactly cheap in terms of running time. However, caller identification is already used by Sun in at least the java.lang.Class and java.lang.ClassLoader classes, using proprietary functionality in sun.reflect.Reflection.getClassCaller(int) to fetch the caller. On the other hand, combining guarded types with dynamic proxies and annotations offers new and exiting possibilities. To us, it seems like an interesting candidate for a reusable pattern component, namely a protection proxy. Annotations can document the types and methods guarded, perhaps even declaring the friends, while dynamic proxies can enforce the caller checks on behalf of the (real) guarded type.
9.3.3. Observer
Establishing pattern functionality in the source code is vital for pattern behaviour, naturally, but also to document pattern intent and structure for maintainability and reuse. Annotations bridges the gap between compile—time and runtime, because they allow the developer to augment source code in user—defined ways that can be processed at compile—time and at runtime, if so specified. The Observer implementation provides a reusable component that supports different kinds of observers without coercing a common observer type. Coercing a common observer type normally requires extensive adaptation by clients and may violate information hiding, analogous to the interface discussion from the previous section. Instead, observer.ObserverManager uses the meta.reflect.@Executor annotation to identify both observers and their notification methods. It also allows the same type to be notified using several different notification methods. The strategy on how to notify a given observer is thus determined by the observer itself, lessening the need for adaptation. This technique is also applicable to several other Behavioural patterns, at least to Chain of Responsibility, Command, Mediator, Strategy, and Visitor, but at the cost of (reusable) components issuing reflective dispatch of the methods in question. Annotation usage is described and illustrated in section 7.1.2.5. Section 8.3.7 provides the detailed description of the Observer implementation.
9.3.3.1. Perspective
It is worth noting that in our experience, some developers refrain from using annotations, and perhaps more so reflection in general. Primary reasons are missing type safety and performance, but also lack of familiarity. However, several well—known frameworks support annotations, such as EJB3, Hibernate, JBoss Seam, Google Guice, etc., and annotation usage seems to continue to spread. The use of JavaBeans has always relied on naming conventions reminiscent of annotated method usage. As of Java 1.4 reflective access is only twice as slow as normal access [Bloch01, p.158] – which is actually pretty decent – but will still slow any application considerably down if used blindly, for example in critical loops. For design patterns, this is usually not an issue. Type safety is a big issue, however, especially since generics have been introduced to heighten type safety. However, annotations need not be used runtime at all. Nonetheless, in our view, wise (and moderate) use of runtime annotations combined with reflection certainly has its uses in the "Gang of Four" patterns, and we think the evaluation exhibits this.
With proper use of annotations, code inspection becomes much easier. Consider being handed a Java project with tens of thousands lines of code and being able to generate a report on, say, all applications of the Observer and Singleton patterns, including places where the standard Singleton conventions are not used. Singleton shows how the singleton.@Singleton annotation is also used to identify Singleton types and behaviour at runtime, but with Sun's Annotation Processing Tool (APT) annotation information can also be utilised at compile—time. The Observer implementation uses annotations in a similar fashion. Report—generation is truly an improvement over "traditional" scattered documentation, especially because annotations are maintained in conjunction with the source code itself. Developers knowing the "Gang of Four" patterns will quickly be able to understand the reasoning behind the solution, because they understand the reasoning behind the patterns and have a complete list of the concrete pattern applications generated by annotation processing. If the annotations furthermore express actual pattern functionality, the pattern implementations become very compact.
The evaluation illustrates how simple annotation usage can identify pattern participants and simple functionality, such as identifying notification methods in Observer. Ernst proposes an extension to Java's annotation system (JSR308) that permits annotations to appear on any use of a type as opposed to Java 6 where annotations are only applicable to declarations of packages, types, methods, fields, and local variables as well as method parameters [Ernst07, p.1,3]. Ernst suggests usage of annotations such as @NotNull and @ReadOnly to create custom type qualifiers that provide extra information about a type or a variable, such as String s = (@NonNull String)o and class UnmodifiableList<T> implements @ReadOnly List<@ReadOnly T> [Ernst07, p.4]. The focus is on static usage, but they can still be used like standard Java 6 annotations because of backward compatibility. Hence, the annotations may be retained at runtime and used reflectively [Ernst07, p.17]. This form of annotations can be used to express pattern participants, properties, and relationships even more detailed. This could be useful as the interactions between participants can be described in detail, for example annotations describing receiver behaviour on methods. For example, the Proxy implementation in this evaluation allows sharing of meta.model.Sequence<E> instances via the Handle/Body idiom (dynamic proxies), where a copy of the shared sequence will be created once a mutator method is invoked. This is described in section 7.1.2.4. Mutator methods are specified via their names and fetched reflectively, which is unsafe and error prone. A @ReadOnly, or rather a @Mutator, annotation could specify this functionality directly in a type—safe fashion on the relevant methods, as in public E current() @ReadOnly {..} and public E next @Mutator {..}. The use of such low—level annotations combined with dynamic proxies may perhaps allow dynamic implementation of some of the "Gang of Four" patterns, particular Behavioural ones. How much this type of annotations will offer compared to the standard use is questionable and would require extensive research to determine.
As a final comment on annotation usage in patterns, note that several CASE tools already have support for the "Gang of Four" patterns, for example Rational Rose. The NetBeans IDE used as secondary IDE in this thesis has tool support to apply all "Gang of Four" patterns, but with the improved modularity offered by annotations, tooling can be much improved. This goes both ways, i.e. code—generation and reverse engineering. Common standards might even evolve in form of annotations used to describe the individual "Gang of Four" patterns.
9.3.4. Proxy and State
Java has built—in support for the Proxy pattern via dynamic proxies. Dynamic proxies depend heavily on reflection, especially reflectively invoking methods. Gamma et al. describe four different types of proxies, namely remote proxies, virtual proxies, protection proxies, and smart references [Gamma95, p.208-209]. All can be implemented using dynamic proxies, and the evaluation provides examples of all but remote proxies, for example Handle/Body idiom as already described (smart reference). The State implementation illustrates how dynamic proxies can be used to simulate dynamic inheritance by changing the target of method invocations at runtime. By changing the target object, the implementation effectively changes. The implementations need not even have the same type, only matching method signatures. Section 7.1.2.4 explains the general usage of dynamic proxies in the pattern implementations. Practical application is illustrated in listing 7.21, where Prototype uses Proxy to make objects prototypical. Section 8.2.7 and 8.3.8 provide the detailed descriptions of the Proxy and State implementations, respectively. Section 8.1.4 provides the detailed implementation for Prototype, illustrating the use of Proxy.
9.3.4.1. Perspective
Dynamic proxies are not merely of academic interest. Annotations are runtime represented by dynamic proxies, for example. Annotation and dynamic proxy collaboration is a very interesting area to pursue concerning the pattern functionality just described in section 9.3.3.1. The evaluation shows that dynamic proxies can be used to implement much of the alternative pattern implementations described by Gamma et al. However, usage may cause unforeseen consequences and change the implementation of the pattern participants considerably. Behavioural patterns can benefit from dynamic proxy usage because it allows the behaviour to change at runtime. This is clearly illustrated in the State implementation. Structural patterns such as Adapter and Decorator can also benefit from dynamic proxies.
Several of the properties associated with dynamic proxies can make them ill suited for pattern implementations. In general, patterns that rely on inheritance as opposed to interface implementation (composition) could be problematic, for example Singleton and Template Method. As with any reflective feature, dynamic proxies should be used with care. They can have tremendous impact on the pattern and application functionality in ways not expected, not even considering the increase in code required to make them work, which may clutter otherwise concise implementations. Such code can be hard to maintain and make pattern implementations harder to understand. This contradicts traditional OO lore and especially forces like Efficiency, Reliability, and Testability as described by Buschmann et al. in section 3.4. Refactoring and component usage as for example the meta packages in this evaluation can help. Consider as part of an API a Creational pattern such as Abstract Factory, Builder, or Factory Method that produces dynamic proxies instead of concrete types (classes and/or interfaces). Creational patterns are often pivotal in any design because they have the responsibility of creating new objects on demand. Dynamic proxies will thus proliferate throughout the application, probably without the client knowing. They will be used in composites, iterators, commands, decorators, etc. Problems related to performance, marshalling, serialization, object identities, testing on and casting to exact classes, etc., could easily arise.
9.3.5. Singleton
The evaluation demonstrates two important, and as far as we know, novel issues regarding the Singleton pattern implemented in Java: 1) As of Java 5, Java has built—in support for Singleton semantics; and 2) sub—classing is possible using guarded types.
The Singleton—as—Single—Constant idiom defined in this thesis illustrates Java's support for the Singleton pattern. By using enumerations as the mean to enforce Singleton behaviour, it is guaranteed that the Singleton type is non—instantiable and has a single global point of access – the singleton constant. This corresponds to the Singleton requirements set forth by Gamma et al. All the desirable traits of enumerations will inherently be expressed in the Singleton type, such as automatic instantiation; serializable; non—cloneable; known at compile—time; etc. On the other hand, the enumeration limitations will also be expressed. The Singleton constant is accessed directly, without the use of method indirection; arguments cannot be supplied at construction time; and it cannot be sub—classed. Section 7.1.1.4 explains the general usage of enumerations in the pattern implementations, and the Singleton—as—Single—Constant idiom is illustrated in listing 7.9. Section 8.1.5 provides the detailed description of the Singleton implementation.
Like Memento, sub—classing of Singleton requires the singleton type to be a guarded type that validates the sub—class caller. Combining this with hiding of static methods, the evaluation shows that singleton inheritance is practically feasible, but must be applied in an Informal way for each applicable singleton type. The same restrictions apply as for Memento, though the Singleton sub—class constructor is already declared protected. A certain level of trust is still required, though. Callers are represented by the meta.reflect.CallerClass and meta.reflect.Caller types and section 7.1.2.1 explains their usage. Singleton sub—classing is illustrated in listing 7.15.
9.3.5.1. Perspective
In the discussion at [PPR, p.JavaSingleton], it is suggested that Java should support Singleton behaviour directly via a single keyword, i.e. that a class can declare it is a singleton like public single class SingletonClass. The use of a keyword clearly states the pattern intent, but Java 6 supports Singleton behaviour via enumerations and can use annotations to clarify the pattern intent. This is illustrated in the evaluation via the singleton.@Singleton annotation. More so, if annotation usage where expanded as discussed in section 9.3.3.1, @Singleton could be enforced wherever the Singleton type is used. On the other hand, since enumerations can be utilised, syntactic sugar in form of a single keyword seems feasible similar to arrays and varargs usage.
An interesting idea is to combine guarded types and annotations. Annotations can be used to specify "friends", particularly if usage of methods can also be annotated.
9.4. Evaluation Approach
To put the experiments and evaluation in perspective, we give a short evaluation of the evaluation approach itself. While the intent of the evaluation approach is to address all pattern functionality in specific elements, it is loosely formulated. Especially the comparative evaluation. This leaves room for interpretation, but it may also collide with the premise that the results can be verified. This is reminiscent of using pattern descriptions. Concrete issues include the functionality to examine, i.e. all information in the Implementation and Sample Code elements, the semantics of the language chosen as the catalyst, and the format of the detailed evaluation. This leaves many variables to the discretion of the evaluator. As the functionality described by Gamma et al. is written in natural language, it is up for interpretation. The features chosen can be an arbitrary sub—set of the language used. How verbose or exacting the written evaluation is is also up to the evaluator. Ergo, while the evaluation approach tries to structure the evaluation, it inherently reflects the lingual form of the patterns it investigates. Even though conclusions can be drawn, they will still be subjective. For example, the choice of language features to investigate and how well the evaluator knows these features will in effect determine the output of the evaluation. This is one of the reasons why we changed focus in this thesis from using several languages on a few patterns to using a single language on all the "Gang of Four" patterns: performing an in—depth evaluation using a language with little to no practical experience does not make sense. Language knowledge and experience is paramount, even more so than experience with the "Gang of Four" patterns. Before this evaluation, there were several patterns we had never implemented "as is", for example Builder, Chain of Responsibility, Interpreter, and Visitor. It is hard to make concrete, but we argue that the implementations at least express the pattern qualities associated with the Implementation and Sample Code elements as listed in table 3.2, i.e. Composibility, Equilibrium, Generativity, and Openness. This is because we adhere to the "Gang of Four" pattern descriptions and to "Best Practices" in Java as described by Bloch [Bloch01].
Amidst conducting the implementations and evaluations, we realised that the Sample Code element really does not provide new information compared to what is written in the Implementation element; it merely provides C++ (and Smalltalk) examples of the topics discussed in the Implementation element. Instead, the Applicability and Consequences elements seem better candidates to include in future evaluations for additional information. The patterns describe the interesting forces in the Consequences element that truly defines the pattern behaviour as in for example Strategy, and Visitor even provides source code in the element. Another example is Proxy that describes the different kind of proxies in the Applicability element, but only shows examples of how to implement few of these in the Implementation and Sample Code elements. The Consequences element further describes interesting proxy features such as copy—on—write that are not illustrated. Still, as the evaluation shows, it is impossible not to include functionality from (almost all) other elements. The point is that the patterns describe more than the source code, and the source code only makes sense within the pattern context. However, performing a structural evaluation based on these suggested elements as well may be difficult because they focus more on natural language and abstractions than on programming languages and practical examples. The evaluation will not become less subjective, which is illustrated by the pattern qualities we identified and associated with the Abstraction pattern element as listed in table 3.2.
A question that arises from an evaluation of this kind is whether the evaluation investigates the "Gang of Four" patterns using a given language, or if it evaluates the language by merely using the "Gang of Four" patterns as the showcase. The evaluation approach tries to set the bar high and effectively asks how well suited a given language is to express the "Gang of Four" pattern abstractions and qualities, albeit only using certain elements from the pattern descriptions. The "Gang of Four" pattern concepts, themes, and functionality are the focus of attention because the language features are used to express them. Nonetheless, the evaluation is undeniably also a testament to the language used, illustrated especially by the comparative evaluation and the high—lights. Because of the extensive focus on implementation related pattern elements, the investigation also becomes very language specific and technical. The Implementation and Sample Code elements primarily use C++ to illustrate pattern functionality, while dynamic languages, e.g. Smalltalk and Self, are used in the Meta—information in these elements to discuss alternative implementations. Little remarks by Gamma et al. regarding (not illustrated) pattern functionality cause verbose (Java 6) implementations and associated evaluations that have to analyse the solution. This is for example the case with the State implementation using dynamic proxies to simulate dynamic inheritance: a paragraph of eight lines [Gamma95, p.309] yields 1000+ lines of code overall (including documentation and blank lines). The evaluation shows that Java 6 can address practically all issues discussed in the examined elements. In that context, Java 6 offers the designer a "better toolbox" to express the pattern functionality. Of course, in real—life applications there are other considerations to make and forces to consider, for example Efficiency and Reliability as discussed in section 3.4. C++ is faster compared to Java 6, and reflection is more error prone than compiled code. Hence, the evaluation does not conclude that Java 6 should be preferred in all contexts. It merely illustrates how well suited the language, e.g. Java 6, is to express the "Gang of Four" abstractions. The rest is up the designer. However, it is important to remember that only parts of the pattern descriptions are investigated. Many of the patterns could justify having a small book dedicated to them, so could many of the Java 6 features. In fact, many of them do.
Another question is how the specific set of features is chosen. The features listed in table 7.1 are selected based on language experience, but also because of prior knowledge regarding the "Gang of Four" patterns. Other features could have been primitive types, assertions, and more focused analysis of polymorphism. Had reflection not been chosen, the evaluation outcome would have been less impressive concerning supported "Gang of Four" functionality. Still, the evaluation clearly expresses the features evaluated, thereby establishing the language context within to judge the evaluation outcome. As table 7.1 illustrates, some patterns utilise many features, while others utilise fewer. This is a testament to pattern granularity and abstraction as in Facade compared to Singleton, but also indicates that it is almost impossible implementation—wise not to favour patterns supporting multiple (and fun) features.
The division between detailed and comparative evaluation is easier to express in structural literary form than it is to conduct in practice. The process of implementing the patterns in effect took the form of Iterative development as described in chapter 2, where discoveries in one pattern would lead to the evolution of other similar patterns and so forth. Because of different pattern abstractions and relationships, the time spent in the different implementations has not been identical. Nonetheless, since all implementations express the pattern functionality, we believe they express the qualities and forces described by Gamma et al. as well. Including the Consequences element in future evaluations would merit a better judgement of this, however.
Conclusion — The evaluation approach offers a way to investigate and judge how well a given language can express the "Gang of Four" functionality expressed in the Implementation and Sample Code elements, but it does not draw any conclusions as to whether the language catalyst should be used in a given scenario. This is – of course – left to the discretion of the designer. The conclusions on the practical features used are subjective, but can be verified using a similar evaluation set—up. The evaluation serves as a tool from which practical experience can be drawn, but it is too loosely formulated to be considered a methodology.
14. | The issues with Java 5 revolve around API usage and not core Java features, for example using the java.util.Collections.newSetFromMap(..) method included as of Java 6. The problems are marked with //ISSUE: comments in the source code as explained in chapter 6. However, the results do not apply to Java 1.4. Compiling with Java 1.4 yields 9.400+ errors and 800+ warnings! This is because many of the features were introduced as of Java 5, i.e. generics, annotations, enumerations, covariant return types, and varargs. |