Outline
Other Material
Most people look to solutions of previous problems that have similar characteristics.
This insight is what is behind design patterns; collections of proven ways to structure the relatinships between objects in the pursuit of a given objective.
This vocabulary is termed a pattern language.
Many patterns are involved with the concepts of dependency we introduced in the previous chapter.
Determining where strong dependencies are necessary, and how to weaken dependencies whenver possible.
An adaptor is used to connect a client (an object that needs a service) with a server (an object that provides the service).
The client requires a certain interface, and while the server provides the necessary functionality, it does not support the interface.
The adapter changes the interface, without actually doing the work.
The adapters in the Java graphics API are not true adapters in this sense of the word.
class MyCollection implements Collection { public boolean isEmpty () { return data.count() == 0; } public int size () { return data.count(); } public void addElement (Object newElement) {data.add(newElement); } public boolean containsElement (Object test) { return data.find(test) != null; } public Object findElement (Object test) { return data.find(test); } private DataBox data = new DataBox(); }
DataBox is some collection that does not support the Collection
interface.
Adapters are often needed to connect software from different vendors.
Patterns themselves have developed their own vocabulary for description:
We will briefly examine a number of common patterns:
How do you provide a client access to elements in a collection, without exposing the structure of the collection.
Solution: Allow clients to manipulate an object that can return the current value and move to the next element in the collection.
Example, Enumerators in Java
interface Enumerator { public boolean hasMoreElements(); public Object nextElement(); } Enumeator e = ...; while (e.hasMoreElements) { Object val = e.nextElement(); ... }
The pattern applies, even if the interface is changed.
How do you simplify the manipulation of many different implementations of the same interface (i.e., iterators).
Solution: Hide creation within a method, have the method declare a return type that is more general than its actual return type.
class SortedList { ... Enumerator elements () { return new SortedListEnumerator(); } ... private class SortedListEnumerator implements Enumerator { ... } }
The method is the ``factory'' in the name. Users don't need to know the exact type the factory returns, only the declared type.
The factory could even return different types, depending upon circumstances.
Allow the client the choice of many alternatives, but each is complex, and you don't want to include code for all.
Solution: Make many implementations of the same interface, and allow the client to select one and give it back to you.
Example: The layout managers in the AWT. Several different layout managers are implemented, and the designer selects and creates one.
Gives the designer flexibility, keeps the code size down.
You want to ensure that there is never more than one instace of a given class.
Solution: Make the constructor private, have a method that returns just one instance, which is held inside the class itself.
class SingletonClass { public: static SingletonClass * oneAndOnly () { return theOne; } private: static SingletonClass * theOne; SingletonClass () { ... } }; // static initialization SingletonClass * SingletonClass::theOne = new SingletonClass();
How do you facilitate creation of complex systems from simple parts?
Solution: Provide a few simple components, and a system to compose components (simple or otherwise) into new components.
Regular expressions are an example, are are type systems, or the nesting of panels within panels in the Java AWT API.
: Allow functionally to be layered around an abstraction, but still dynamically changable.
Solution: Combine inheritance and composition. By making an object that both subclasses from anther class and holds an instance of the class, can add new behavior while referring all other behavior to the original class.
Example Input Streams in the Java I/O System
// a buffered input stream is-an input stream class BufferedInputStream extends InputStream { public BufferedInputStream (InputStream s) { data = s; } ... // and a buffered input stream has-an input stream private InputStream data; }
An instance of BufferedInputStream can wrap around any other type of InputStream, and simply adds a little bit new functionality.
: You have variation in two or more polymorphic variables.
Solution: Make each a receiver in turn, each message ties down one source of variation.
Example, suppose we have a hierarchy of Shapes (Triangle, Square) and Device (Printer, Terminal). Two variables, one a Shape and one a Device.
First, pass a message to the device, passing the shape as argument:
Shape aShape = ... ; Device aDevice = ...; aDevice.display(aShape); function Printer.display (Shape aShape) begin aShape.displayOnPrinter (self); end; function Terminal.display (Shape aShape) begin aShape.displayOnTerminal (self); end;
One message fixes the device, but how to fix the shape?
Each subclass of Shape must implement methods for each output device:
class Triangle : public Shape { public: Triangle (Point, Point, Point); // ... virtual void displayOnPrinter (Printer); virtual void displayOnTerminal (Terminal); // ... private: Point p1, p2, p3; }; void Triangle.displayOnPrinter (Printer p) { // printer-specific code to // display triangle // ... } void Triangle.displayOnTerminal (Terminal t) { // terminal-specific code to // display triangle // ... }
How to hide unimportant communication details, such as a network, from the client.
Solution: A proxy uses the interface that the client expects, but passes messages over the network to the server, gets back the response, and passes it to the client. The client is therefore hidden from the network details.
Similar in some ways to adaptor, but here the intermediary and the server can have the same interface.
: Actual work is performed by two or more objects, but you want to hide this level of complexity from the client.
Solution: Create a facade object that receives the messages, but passes commands on to the workers for completion.
Also similar to adapter and proxy.
How do you dynamically (at run time) add and remove connections between objects.
Solution: An Observer Manager implements the following protocol:
In this way neither the observer nor the observed object need know the existance of the other.