When experts need to solve a problem, they seldom invent a totally new solution. More often they will recall a similar problem they have solved previously and reuse the essential aspects of the old solution to solve the new problem. They tend to think in problem-solution pairs.
Identifying the essential aspects of specific problem-solution pairs leads to descriptions of problem-solving patterns that can be reused.
The concept of a pattern as used in software architecture is borrowed from the field of (building) architecture, in particular from the writings of architect Christopher Alexander.
Where software architecture is concerned, the concept of a pattern described here is essentially the same concept as an architectural style or architectural idiom in the Shaw and Garlan book.
In general, patterns have the following characteristics [Buschmann]:
Various authors use different formats (i.e., "languages") for describing patterns. Typically a pattern will be described with a schema that includes at least the following three parts [Buschmann]:
The Context section describes the situation in which the design problem arises.
The Problem section describes the problem that arises repeatedly in the context.
In particular, the description describes the set of forces repeatedly arising in the context. A force is some aspect of the problem that must be considered when attempting a solution. Example types of forces include:
Forces may complementary (i.e., can be achieved simultaneously) or contradictory (i.e., can only be balanced).
The Solution section describes a proven solution to the problem.
The solution specifies a configuration of elements to balance the forces associated with the problem.
Patterns can be grouped into three categories according to their level of abstraction [Buschmann]:
Shaw and Garlan normally use the term architectural style for the architectural pattern concept described here.
An architectural pattern is a high-level abstraction. The choice of the architectural pattern to be used is a fundamental design decision in the development of a software system. It determines the system-wide structure and constrains the design choices available for the various subsystems. It is, in general, independent of the implementation language to be used.
An example of an architectural pattern is the Pipes and Filters pattern. In Unix for instance, a filter is a program that reads a stream of bytes from its standard input and writes a transformed stream to its standard output. These programs can be chained together with the output of one filter becoming the input of the next filter in the sequence via the pipe mechanism. Larger systems can thus be constructed from simple components that otherwise operate independently of one another.
Other example architectural patterns are Layered systems, Blackboards, and the Model-View-Controller pattern for graphical user interfaces.
A design pattern is a mid-level abstraction. The choice of a design pattern does not affect the fundamental structure of the software system, but it does affect the structure of a subsystem. Like the architectural pattern, the design pattern tends to be independent of the implementation language to be used.
Examples of design patterns include the following:
For instance, one of the implementations of the Ranked Sequence abstract data type we
studied uses the Adapter pattern. The
VectorRankedSeq.java
class adapts the built-in Java
Vector
class to match the specification of the
RankedSequence
interface.
The paper "Creating Applications from Components: A Manufacturing Framework Design" by H. A. Schmid (IEEE Software, Nov. 1996) also gives an example of how the Adapter pattern can be used in an application framework. The paper shows that a portal robot machine class can be adapted for use as a transport service class.
For instance, a Java object that implements the
Iterator
interface is returned by the
iterator()
method of a Vector
object;
successive calls of the next()
method of the
Iterator
object returns successive elements of the
Vector
object.
Note: The Iterator
interface is part of the Java
Collections framework. There is an older Java iterator mechanism,
represented by the Enumeration
interface, which was
implemented in versions of Java before 1.2. A number of preexisting
classes also support methods that return Enumeration
objects.
The Ranked Sequence case study
includes iterators in the form of Java Enumeration
objects. In the
ArrayRankedSeq
and
DoubleLinkRankedSeq
classes, these iterators are
implemented directly; the
VectorRankedSeq
implementation uses the iterator for
the underlying Vector
.
We also see this pattern used in the Schmid article; the third
transformation involved breaking up the application logic class
ProcessingControl
into several subclasses of a new
ProcessingStrategy
class. The specific processing
strategy could then be selected dynamically based on the specific
part-processing task.
An idiom is a low-level abstraction. It is usually a language-specific pattern that deals with some aspects of both design and implementation.
In some sense, use of a consistent program coding and formatting style can be considered an idiom for the language being used. Such a style would provide guidelines for naming variables, laying out declarations, indenting control structures, ordering the features of a class, determining how values are returned, and so forth. A good style that is used consistently makes a program easier to understand than otherwise would be the case.
In Java, the language-specific iterator defined to implement the
Iterator
interface can be considered an idiom. It is a
language-specific instance of the more general Iterator design
pattern.
Another example of an idiom is the use of the Counted Pointer (or Counted Body or Reference Counting) technique for storage management of shared objects in C++. In this idiom, we control access to a shared object through two classes, a Body (representation) class and a Handle (access) class.
An object of the Body class holds the shared object and a count of the number of references to the object.
An object of a Handle class holds a direct reference to a
body object; all other parts of the program must access the body
indirectly through handle class methods. The handle methods can
increment the reference count when a new reference is created and
decrement the count when a reference is freed. When a reference count
goes to zero, the shared object and its body can be deleted. Often
the programmer using this pattern will want to override the
operator->
of the handle class to give more transparent
access to the shared object.
A variant of the Counted Pointer idiom can be used to implement a "copy on write" mechanism. That is, the body is shared as long as only "read" access is needed, but a copy is created whenever one of the holders makes a change to the state of the object.
The first version of these notes was written during the spring 1998 semester for the Software Architecture class (offered as ENGR 691, Special Topics in Engineering Science).
UP to ENGR 691 Lecture Notes root document?