Tik-76.270, Research Seminar: Java-based Software Technologies
Mika Kujala
mika.kujala@research.nokia.com
For the last three years design patterns have been one of the hottest topics in the field of object-oriented software engineering. Design patterns are pieces of literature that describe simple, flexible, elegant, reusable and understandable solutions to specific problems. They also aim to create a common vocabulary which enables designers to work at a higher level of abstraction. As an implementation language, Java supports the design pattern principles by supporting inheritance and interfaces and providing full portability with clean object-oriented abstractions. This paper introduces the concept of design patterns and demonstrates their usefulness with several common pattern examples implemented with the Java programming language.
Designing reusable object-oriented software is a difficult task.
Especially this applies to application frameworks that aim to provide
the highest degree of reusability. This is commonly achieved by providing
the developer a set of co-operating classes that give the opportunity
to utilize an application domain specific design by extending the
framework or completing it. Also, using object-orientation in design
and programming does not automatically guarantee reusability of systems.
Creating objects by specifying a class directly can lead to code that
is dependent on a particular object representation or even hardware and
cause inability to extend or modify functionality of objects [2].
Design patterns for software development are one of the latest topics adopted by the object-oriented community. They describe simple, flexible, elegant, reusable and understandable solutions to specific problems in object-oriented design. One of the basic goals of design patterns is to create a body of literature to help software developers resolve common difficult problems encountered throughout all of software engineering and development. They force designers to work at a higher level of abstraction and thus to acquire a deeper understanding of the problem at hand. A common vocabulary is an essential step towards making software development a true engineering discipline.
Formally codifying known solutions and their relationships makes it possible to successfully capture the knowledge which contains our understanding of good architectures that meet the needs of their users. Forming a common pattern language for conveying the structures and mechanisms of our architectures allows us to reason about them on a higher level of abstraction. The primary focus is not so much on technology as it is on creating a discipline to document and support sound engineering architecture and design.
As the methodologies for framework design and reuse are being improved, the role and popularity of the Java programming language seem to increase. This report presents some of the benefits of using Java as an implementation language and presents examples of some of the basic design pattern implementations in the form of Java code fragments.
Design patterns have their roots in urban design and building architecture. In his book A Pattern Language: Towns, Buildings, Construction(1997), the architect and urban planner Christopher Alexander attempted to summarize the knowledge of experienced architects, as well as common sense rules and traditions of builders in various cultures, into a structured set of patterns that could guide designers to achieve elegant and practicality commonly found in all successful designs. Alexander's book contains over 250 patterns that are represented by describing first a given design problem, then discussing its applicability constraints and finally presenting a solution to the problem.
The beginning of the design pattern movement in software design can be set to the year 1987 when two researchers Kent Beck and Ward Cunningham applied the patterns concept to GUI development in Smalltalk. They decided to use some of Alexander's ideas to develop a small five pattern language for guiding novice Smalltalk programmers. They wrote up the results and presented them at OOPSLA'87 in Orlando in the paper Using Pattern Languages for Object-Oriented Programs [4]. In 1992, Ralph Johnson was the first to notice the close relationship between design patterns and frameworks. His proposal was targeted to address the question of using design patterns as an aid to document application frameworks [2].
For last three years the idea of design patterns has been the hottest topic in object-oriented software engineering. There is an annual conference on patterns Pattern Languages of Program Design (PloP), an active WWW-site and many mailing lists. The most influential of the many books published about design patterns is Design Patterns: Elements of Reusable Object-Oriented Software by the so-called Gang of Four, Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides [1]. This book describes the idea behind the design patterns and presents an impressive set of general and reusable micro architectures in a compact catalog format.
A design pattern is a piece of literature that describes a design problem and a general solution to the problem in a particular context. Several definitions have been presented by various people of the software patterns community [3]. In Understanding and Using Patterns in Software Development, Dirk Riehle and Heinz Zullighoven give a very broadly applicable definition of the term "pattern": A pattern is the abstraction from a concrete form which keeps recurring in specific non-arbitrary contexts.
The above authors point out that the concept of a pattern is aimed at solving problems in design. The concrete form which recurs is that of a solution to a recurring problem. Using the pattern form, the description of the solution tries to capture the essential insight which it embodies, so that others may learn from it, and make use of it in similar situations. The pattern is also given a name, which serves as a conceptual tool, to facilitate discussing at a higher level of abstraction.
Alexander describes a pattern as follows: Each pattern is a three-part rule, which expresses a relation between a certain context, a problem, and a solution. As an element in the world, each pattern is a relationship between a certain context, a certain system of forces which occurs repeatedly in that context, and a certain spatial configuration which allows these forces to resolve themselves. The pattern is at the same time a thing, which happens in the world, and the rule which tells us how to create that thing, and when we must create it [1].
To summarize all these definitions, we see that a pattern involves a general description of a recurring solution to a recurring problem with various goals and constraints. In addition to identifying a solution, a pattern also explains why the solution is needed.
A large variety of established pattern forms exist. Usually a design pattern is represented by a template for a consistent format with at least four essential elements:
- The name: Describes the intent of the pattern with common vocabulary
- The problem and its context: Including motivation, symptoms, conditions and constraints
- Solution: Works as a template describing elements, roles, relationships, responsibilities and collaborations presented as text and charts
- The consequences: Results, trade-offs, evaluation of alternatives, implementation issues, impact on reusability, flexibility and portability
The GOF ("Gang of Four") form established in the book Design Patterns: Elements of Reusable Object-Oriented Software presents a more detailed element structure for design patterns that includes the following elements: Pattern name and Classification, Intent, Also known as, Motivation, Applicability, Structure, Participants, Collaborations, Consequences, Implementation, Sample Code, Known Uses, Related Patterns [1].
In order to organize design patterns to sets of related patterns, the GOF book also presents a classification of the patterns that is based on two criteria purpose and scope. Patterns can have either creational, structural or behavioral purpose. The scope specifies whether the pattern applies primarily to classes or objects[1].
A close relationship exists between design patterns and frameworks. They both address the need for reuse in object-oriented software development. However, there are also some major differences.
Frameworks are code, but only example instantiations of patterns can be embodied in code. In other words, design patterns are more abstract than frameworks. Also, design patterns are smaller architectural elements than frameworks that can consist of many interwoven design patterns. A framework is always associated with a certain application domain; design patterns are less specialized. A framework dictates the whole architecture for applications derived from it. Design patterns provide a form of reuse for abstract design solutions. Combining these two methods produces the state-of-the-art knowledge of the object-oriented research [2].
If a design pattern represents a best practice, then an anti-pattern represents a 'lesson-learned'. The concept of anti-patterns was initially proposed by Andrew Koenig, a C++ pioneer at Bell Labs, who proposed the following two varieties:
1. Those that describe a bad solution to a problem which resulted in a bad situation
2. Those that describe how to get out of a bad situation and how to proceed from there to a good solution
Anti-patterns are valuable because it is often just as important to see and understand bad solutions as it is to see and understand good ones. The following statement is extracted from the AntiPatterns page at the WikiWiki Web [9]:
The study of anti-patterns is an important research activity. The presence of "good" patterns in a successful system is not enough; you also must show that those patterns are absent in unsuccessful systems. Likewise, it is useful to show the presence of certain patterns (anti-patterns) in unsuccessful systems, and their absence in successful systems.
Because of its full portability, clean object-oriented abstractions and a number of standard packages, e.g. for graphical user interfaces, the Java language can readily be adopted as the implementation language for design pattern based solutions. As a programming language Java supports the design patterns principles very well, as it supports inheritance and interfaces. The Java interface/implementation model is extremely useful in design patterns context.
The Java AWT itself is an example of a framework that was from the beginning written to explicitly follow the design pattern principles. At the very core of AWT is the Composite pattern which makes it possible to compose complex GUI objects recursively from simple subparts. Also, the Strategy pattern is used in AWT to layout parts on a canvas or container, the Chain of Responsibility pattern is used for event handling, the Bridge pattern for decoupling the abstract AWT components from platform specific widgets, and the Abstract Factory pattern to create families of related widgets. The following chapters present some of the most common design patterns as Java implementations.
An interface is used to encapsulate a collection of methods without explicitly implementing their bodies. Interfaces are more abstract than classes since they don't say anything at all about representation or code. All they do is describe public operations. Interfaces solve some of the same problems that multiple inheritance does without as much overhead at runtime. However, because interfaces involve dynamic method binding, there is a small performance penalty to using them. Using interfaces allows several classes to share a programming interface without having to be fully aware of each other's implementation. The following example describes the
java.lang.Runnable
interface:
public interface Runnable { public abstract void run(); }
Interfaces turn out to be a very useful concept. They are important tools for stepping up from writing occasional classes to writing extensible packages and frameworks of classes that form the basis for suites of applications. Some of the benefits of using Interfaces are:
- Classes that are otherwise totally unrelated can support the same interface if they happen to offer the same service.
- In Java, a class can have only one superclass, but can claim to implement any number of interfaces.
- Interfaces form a useful basis for remote method invocations; i.e., messages to objects that reside on different computers or processes.
Interfaces are used almost like classes in Java. For example, you can write a method that accepts any object obeying a stated interface and invoke methods on it, as in:
class Foo { void doIt(Runnable r) { r.run(); } }
Subclasses can also implement unrelated interfaces. For example, to create a subclass of some pre-exisiting class that also implements
Runnable
to execute a
particular method when run()
is invoked:
class URLReader { protected String url_; protected byte[] buffer_; public URLReader(String url, byte[] buffer) { url_ = url; buffer_ = buffer; } public void read() { /* read into buffer */ } } class RunnableURLReader extends URLReader implements Runnable { public RunnableURLReader(String url, byte[] buffer) { super(url, buffer); } public void run() { read(); } }
An Abstract class forms an interesting and useful middle ground between interfaces and classes. Abstract classes are different than ordinary classes in that they declare one or more interface-style
abstract
methods in addition to normal
methods and instance variables. Such classes must be explicitly declared
as abstract
. For example:
abstract class MachinePart { protected String partID_; public String partID() { return partID_; } protected MachinePart(String id) { partID_ = id; } public abstract boolean canConnectTo(MachinePart other); }
Here, the
MachinePart
class sets down a common representation for partIDs
that holds for all subclasses. But the declaration of
canConnectTo
is abstract and has to be
implemented differently in every public subclass. Every
MachinePart
subclass gets its
partID
mechanics for free, but must
specially implement canConnectTo
. And
as usual, it can further extend the class or even implement additional
interfaces:
class Gadget extends MachinePart { public Gadget(String id) { super(id); } public boolean canConnectTo(MachinePart other) { if (other instanceof Gadget) return true; else if (other.partID().startsWith("Universal")) return true; else return false; } public void specialGadgetMethod() { ... } }
Abstract classes are like interfaces in one other sense: Neither are directly instantiable via
new. T
he reason they are not instantiable is that there is no
code supporting one or more methods, so no such object can be constructed.
This is true even though abstract classes often have constructors. But
these are called only from subclass constructors. There are several
further variations on this interplay between interfaces and classes.
For example, it is legal in Java to say that an abstract
class implements
an interface
even though it doesn't
actually define one or more of the methods, but instead leaves them for
its own subclasses to define. Such odd-sounding techniques tend to
become standard tricks of the trade when designing large frameworks
and packages.
The fact that interfaces are not instantiable turns out to be a useful property. It forces the separation of the notions of calling methods from constructing objects. A client of an interface doesn't really care about the particular object or its immediate implementation class that performs a service. So it has no business invoking a constructor declared within a particular implementation class. Instead, when designing sets of components via interfaces, you can create factories.
A factory class exists just to help create instances of other classes. Factories often have methods that produce instances of several related classes, but all in a compatible way. Factories should themselves be defined via interfaces, so the client need not know which particular factory object it is using. Ideally, all such matters can be reduced to a single call to construct the appropriate 'master' factory in a client application. Among the most popular examples of factories are UI toolkits that are designed to run on different windowing systems. There might be interfaces for things like:
interface ScrollBar { ... } interface MenuBar { ... } ...
And associated classes implementing them on different windowing systems:
class MotifScrollBar implements ScrollBar { ... } class Win95ScrollBar implements ScrollBar { ... } ...
And a factory interface that also doesn't commit to representation:
interface Factory { public abstract ScrollBar newScrollBar(); public abstract MenuBar newMenuBar(); ... }
But implementation classes that do:
class MotifFactory implements Factory { public ScrollBar newScrollBar() { return new MotifScrollBar(...); } ... }
Finally, to get an application up and running on different systems, only one bit of implementation-dependent code is required:
class App { public static void main(String args[]) { Factory wf = null; if (args[0].equals("Motif")) wf = new MotifFactory(); else ... startApp(wf); } }
All other objects can construct widgets using the factory passed around or kept in a central data structure, never knowing or caring what windowing system they happen to be running on.
The Java AWT uses a strategy that is similar in concept but different in detail for achieving implementation independence, via a set of 'peer' implementation classes that are specialized for the platform that the Java session is being run on, and instantiated when user-level AWT objects are constructed.
When designing standardized interfaces, existing concrete classes do not necessarily implement the desired interface. For example, they might have methods with slightly different names than defined in the interface. If these concrete classes cannot be modified to fix such problems, it is possible to build an Adaptor class that acts as a middle layer to translate away all the incompatibilities. For example, suppose you have a
Performer
class that
supports method perform()
, which meets
all the qualifications of being usable as a Runnable
except for the name mismatch. You can build an Adaptor so
it can be used in a Thread by some User:
class AdaptedPerformer implements Runnable { private Performer performer_; public AdaptedPerformer(Performer p) { performer_ = p; } public void run() { performer_.perform(); } } class User { ... public void doIt(Performer p) { (new Thread(new AdaptedPerformer(p))).start(); } }
This is only one of many common contexts for building Adaptors (or views and wrappers), which form the basis for several related patterns in the GOF book.
In this delegative style of class construction, the publically accessible 'outer' class forwards all methods to one or more 'inner' delegates, relaying back replies, performing some simple translation (name changes, result filtering etc) before and/or after the inner calls. This style is particularly common in concurrent Java programming (the Coordinator pattern) since it provides an opportunity for performing additional concurrency control over the inner object.
This manual implementation of delegation can even be used as a substitute for subclassing, by having each 'derived' class hold a reference to an instance of its 'base' class, forwarding it all 'inherited' operations. This method can be used to obtain the effects of C++-style multiple inheritance in Java. Delegation can also be more flexible than subclassing, since 'derived' objects can even change their 'bases' dynamically.
Lately, the reuse of abstract design solutions has gained great success
in the form of design patterns. They provide a higher level of abstraction
in terms of design vocabulary and aim to convey expert knowledge about
best practices in software design.
On the other hand, the rapid rise in popularity of software patterns has caused them to be frequently 'overhyped'. Patterns have achieved buzzword status. Within the object-oriented software community it is immensely popular to use the word "pattern" in order to get an audience. However, not every solution, algorithm, best practice, maxim, or heuristic constitutes a pattern. Even if something appears to have all the requisite pattern components, it should not be considered a pattern until it has been verified to be a recurring phenomenon.
Design patterns represent distilled experience which convey expert insight and knowledge to inexpert developers. They help in creating the foundation of a shared architectural vision. In order to develop object-oriented software engineering into a mature engineering discipline these proven "best practices" and "lessons learned" must be aggressively and formally documented as patterns and anti-patterns. Once a solution has been expressed in pattern form, it may then be applied and reapplied to other contexts, which facilitates widespread reuse across the entire spectrum of software engineering. Patterns are extremely valuable tools for capturing and communicating acquired knowledge and experience to improve software quality and productivity by addressing fundamental issues in the development of software.
Although the first well-defined and documented design patterns were usually presented with Smalltalk or C++ sample code, the discussion and samples nowadays are more and more often presented with Java. The reason for this can be connected to the current popularity of the Java language within the object-oriented software community and to the properties of the language itself. As a true object-oriented programming language, Java provides several mechanisms that make it well-suited for design pattern implementations. As noted earlier, especially the interface/implementation model in Java is extremely useful in the context of design patterns.
1] Gamma et al., Design patterns: Elements
of Reusable Object-Oriented Software. Addison-Wesley, 1995
[2] Viljamaa J., Tools Supporting
the Use of Design Patterns in Frameworks. Report C-1997-25,
Department of Computer Science, University of Helsinki, 1997.
[3] Appleton B.,Patterns and Software: Essential Concepts and Terminology,
http://www.enteract.com/~bradapp/[4] Beck K., Cunningham W., Using Pattern Languages for Object-Oriented Program,
http://c2.com/doc/oopsla87.html[5] Riehle D.,Understanding and Using Patterns in Software Development,
http://www.ubs.com/cgi-bin/framer.pl?/info/ubilab/staff/e_riehle.htm[6] Vlissides J., Pattern hatching, Perspective from the "Gang of Four"
http://www.sigs.com/publications/docs/cppr/95/cppr9503v.html[7] Brown K., Crossing Chasms: The architectural patterns,
http://www.ksccary.com/kbrown.htm[8] Patterns home page,
http://hillside.net/patterns/patterns.html[9] AntiPatterns WikiWikiWeb page,
http://c2.com/cgi/wiki?AntiPatterns