Nothing New Under the Sun
A design pattern is a way of doing things
Porches
Nested loops
Too fuzzy to put in a library
But a great way to talk about design issues
The architect Christopher Alexander invented the term
Architectural styles can be described as pattern languages
Because certain patterns usually occur together
The "Gang of Four" book (1994)
Applied the notion to object-oriented programming
Named many widely-used patterns
Today
Dozens of books, conferences, etc.
Terms are widely (if inaccurately) used
Format
Pattern name
Synopsis: a short summary of the pattern
Context: what problem does the pattern address?
Often presented as an example
Forces: what factors influence the decision about whether or not to use this pattern?
Solution: what is the general-purpose solution to the problem?
Consequences: what are the results (good and bad) of using this pattern?
Example
Related Patterns
Abstract Factory
Synopsis: create instances of related classes
Context: want to separate main application from details of specific framework classes
E.g. separate parser from objects being created
Or build GUI to run on Windows, Unix, and cellphones
Forces:
A system that works with multiple products should not contain product-specific code
It should not be possible to create component A from family X, but component B from family Y
Creation mechanism should be extensible, so that it can support new platforms in future
Solution:
Define one interface for each type of component
Create a an abstract factory class with one creation method for each interface
Derive one concrete factory class for each platform, filling in the creation methods
Example:
Graph parser that can produce graph nodes of different (user-defined) types
Abstract factory that created different objects for testing assignment
Prototype
Synopsis: create objects by cloning, without knowing exactly what is being cloned
Context: want to separate main application from details of specific classes being manipulated
E.g. building a WYSIWYG editor for web pages
Or creating instructions for a virtual machine
Forces:
A system should be able to create objects without knowing their exact class
Creation mechanism should be extensible, so that it can handle new objects in future
Solution:
Define an interface that all prototype objects must implement
Give applications a way to register prototypes
When a new object is needed, clone the appropriate prototype
Example:
Graph parser that can produce graph nodes of different (user-defined) types
Allow applications to extend a virtual machine's instruction set by registering new instructions
Wait A Second...
Abstract Factory and Prototype sound very similar
Both create objects without knowing exactly what they are
Both can be extended by users after deployment
How do you tell which one to use when?
Look at what else these classes need to do
Be consistent with existing code
Build up personal experience
Singleton
Synopsis: ensure that only one instance of a class is created
Context: want every object that uses a class to use the same instance of that class
E.g. the graphics driver for the screen
Forces:
There must be exactly one instance of the class
All objects that use the class must have access to that instance
Solution:
Make the constructor private so that other classes can't get at it
Provide a static method for construction
Have that method keep a copy of the first object it creates, and re-use it on subsequent calls
Example:
class GraphicsDriver {
private GraphicsDriver() {
...construct graphics driver...
}
public static GraphicsDriver getInstance() {
if (sInstance == null) {
sInstance = new GraphicsDriver();
}
return sInstance;
}
private static GraphicsDriver sInstance;
}
class Application {
public static void main(String[] args) {
GraphicsDriver gd = GraphicsDriver.getInstance();
gd.clearScreen();
doWork();
}
public static void doWork() {
while (working) {
GraphicsDriver gd = GraphicsDriver.getInstance();
...draw things...
gd.display();
}
}
}
Singleton Commentary
Why not make the instance member variable public, and access it directly?
Because some day you might want one per thread, or one per monitor in a multi-headed display, or...
Code evolves
Design patterns reflect experts' experience with managing that evolution
Why not create the instance variable in a static block?
Might not have necessary parameters
Can't control order in which static initialization blocks are executed
Filter
Synopsis: dynamically connect objects to transform a data stream
Context: some computations most easily expressed as a sequence of transformations
E.g. image processing
Unix commands joined by pipes
Also allows uses to mix and match operations easily
Forces:
Every filter should read and write the same format, so that they can be chained together
It should be possible to chain filters together in arbitrary orders
Solution:
Have each filter implement an interface that reads and writes data
Define special cases for sources (that only write) and sinks (that only read)
Provide filters with a way to signal that they have nothing more to produce
Example:
The multi-filter example from earlier in the course
The standard Unix command line tools
Composite
Synopsis: build complex hierarchies by making sub-hierarchies look like individual objects
Context: want to be able to treat nested collections uniformly
E.g. combine tests and test suites
Or create documents that contain tables containing text containing tables
Forces:
Want other classes to be able to treat collections and individual objects uniformly
Want to minimize the number of special cases that collections have to be aware of
Solution:
Create an interface (or abstract class) defining the properties of both individuals and collections
Have individuals and collections implement this interface
Have collections treat everything they contain as instances of this interface
Example:
In JUnit, TestCase and TestSuite both implement Test
TestSuite contains zero or more Tests
So it can contain both test cases and test suites
Regions in spreadsheets
Individual cells return their own values
Larger regions return the sum, average, etc.
Visitor
Synopsis: separate traversal of a complex structure from operations on that structure
Context: want an easy way to do walk around a complex structure
E.g. visit each node in a graph once
Forces:
Many different operations may need to be performed
Structure is complex enough that visiting elements is error-prone
The types of objects in the structure, and the ways they are connected, change infrequently
So it's worth investing time in building a support tool
Solution #1: create an iterator class with do-nothing methods that are called at specific times
This class takes care of things like remembering which nodes have already been seen
Users derive their own iterators from this, filling in methods to do what they want
Solution #2: create one iterator class that takes a callback argument
Callback only provides methods that do things
Iterator calls these at appropriate times
Solution #1 is simpler, but:
Some languages (e.g. C) don't support derivation, so you have to use solution #2
Solution #2 allows you to re-use callbacks with different kinds of collections
Starts to look like an inside-out Filter
Example:
Graph visitor
Persistence
Adapter
Synopsis: implement an interface known to one set of classes so that they can communicate with other objects they don't know about
Context: want to use a class in a way that its original author didn't anticipate
E.g. write data to a string instead of to a file
Or apply regular expressions to streams instead of to strings
Forces:
You want to use a class as though it implemented an interface that it doesn't actually implement
You do not want to modify or extend that class
You can translate the operations you want to perform to the ones the class actually implements
Solution: create an adapter that implements the interface you want, and calls the methods the class has
Example: both Python and Java provide "string I/O" classes
def testStream(input, output):
lineIn = input.readline()
while lineIn:
lineOut = someTest(lineIn)
output.write(lineOut)
lineIn = input.readline()
# test with real streams --- relies on external files
# and 'diff' to check results
for arg in sys.argv[1:]:
input = open(arg, 'r')
testStream(input, sys.stdout)
input.close()
# better: test with canned input and output (self-contained)
sampleInput = "a\nb\nc\n"
sampleOutput = "AA\nBB\nCC\n"
input = StringIO(sampleInput)
output = StringIO()
testStream(input, output)
assert output.getvalue() == sampleOutput
Other Favorites
Cache: store temporary copies of objects locally to improve performance
Command: represent actions as objects
Give them "combine" and "undo" methods
This is how editors work
State: record state of program as object so that it can be re-started
Null Object: use an object that does nothing in place of null
Saves testing that object isn't null before doing operations
Proxy: use one object as an interface to another
Differs from Adapter in that the proxy object has the same interface as what it's proxying for
Typically, the proxy is local, and the real object is on another machine
And Many, Many More
Lots of books, on-line collections, conferences, etc.
Quality is uneven
But that's true of everything
Not just about object-oriented design
User interface patterns
Business patterns
Anti-patterns (things to avoid)
In practice, serve two purposes:
Communication: a concise way for designers to communicate with each other
And argue out exactly what they mean
Education: gives them a way to communicate what they know to newcomers
Like you
If you buy one book for this course, buy a book on design patterns
Don't expect to connect them all to your own experience the first time
But keep them in mind as you work on other courses
"Hey, I know how to do this!"
$Id: patterns.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $