Patterns 2 : Structural patterns
Some of this material is from the java patterns book by James cooper.*
Adapter pattern
The Adapter pattern is used to convert the programming interface of one class into that of another. We use adapters whenever we want unrelated classes to work together in a single program. The concept of an adapter is thus pretty simple; we write a class that has the desired interface and then make it communicate with the class that has a different interface.
There are two ways to do this: by inheritance, and by object composition. In the first case, we derive a new class from the nonconforming one and add the methods we need to make the new derived class match the desired interface. The other way is to include the original class inside the new one and create the methods to translate calls within the new class. These two approaches, termed class adapters and object adapters are both fairly easy to implement in Java.
Bridge pattern
The Bridge pattern is used to separate the interface of class from its implementation, so that either can be varied separately. At first sight, the bridge pattern looks much like the Adapter pattern, in that a class is used to convert one kind of interface to another. However, the intent of the Adapter pattern is to make one or more classes’ interfaces look the same as that of a particular class. The Bridge pattern is designed to separate a class’s interface from its implementation, so that you can vary or replace the implementation without changing the client code.
Consequences of the Bridge Pattern
1. The Bridge pattern is intended to keep the interface to your client program constant while allowing you to change the actual kind of class you display or use. This can prevent you from recompiling a complicated set of user interface modules, and only require that you recompile the bridge itself and the actual end display class.
2. You can extend the implementation class and the bridge class separately, and usually without much interaction with each other.
3. You can hide implementation details from the client program much more easily.
Composite pattern.
Frequently programmers develop systems in which a component may be an individual object or it may represent a collection of objects. The Composite pattern is designed to accommodate both cases. You can use the Composite to build part-whole hierarchies or to construct data representations of trees. In summary, a composite is a collection of objects, any one of which may be either a composite, or just a primitive object. In tree nomenclature, some objects may be nodes with additional branches and some may be leaves.
The problem that develops is the dichotomy between having a single, simple interface to access all the objects in a composite, and the ability to distinguish between nodes and leaves. Nodes have children and can have children added to them, while leaves do not at the moment have children, and in some implementations may be prevented from having children added to them.
Some authors have suggested creating a separate interface for nodes
and leaves, where a leaf could have the methods
public String getName();
public String getValue();
and a node could have the additional methods:
public Enumeration elements();
public Node getChild(String nodeName);
public void add(Object obj);
public void remove(Object obj);
This then leaves us with the programming problem of deciding which elements will be which when we construct the composite.
However, Design Patterns suggests that each element should have the same interface, whether
it is a composite or a primitive element. This is easier to accomplish, but we are left with the question of what the getChild() operation should accomplish when the object is actually a leaf.
Java makes this quite easy for us, since every node or leaf can return an Enumeration of the contents of the Vector where the children are stored. If there are no children, the hasMoreElements() method returns false at once. Thus, if we simply obtain the Enumeration from each element, we can quickly determine whether it has any children by checking the
hasMoreElements() method.
Decorator Pattern
The Decorator pattern provides us with a way to modify the behavior of individual objects without having to create a new derived class. Suppose we have a program that uses eight objects, but three of them need an additional feature. You could create a derived class for each of these objects, and in many cases this would be a perfectly acceptable solution. However, if each of these three objects require different modifications, this would mean creating three derived classes. Further, if one of the classes has features of both of the other classes, you begin to create a complexity that is both confusing and unnecessary.
For example, suppose we wanted to draw a special border around some of the buttons in a toolbar. If we created a new derived button class, this means that all of the buttons in this new class would always have this same new border, when this might not be our intent. Instead, we create a Decorator class that decorates the buttons. Then we derive any number of specific Decorators from the main Decorator class, each of which performs a specific kind of decoration.
In order to decorate a button, the Decorator has to be an object derived from the visual environment, so it can receive paint method calls and forward calls to other useful graphic methods to the object that it is decorating. This is another case where object containment is favored over object inheritance. The decorator is a graphical object, but it contains the object it is decorating. It may intercept some graphical method calls, perform some additional computation and may pass them on to the underlying object it is decorating.
Non-Visual Decorators
Decorators, of course, are not limited to objects that enhance visual classes. You can add or modify the methods of any object in a similar fashion. In fact, non-visual objects are usually easier to decorate, because there are usually fewer methods to intercept and forward.
While coming up with a simple example is difficult, a series of Decorators do occur naturally in the java.io classes. Note the following in the Java documentation:
The class FilterInputStream itself simply overrides all methods of InputStream with versions that pass all requests to the underlying input stream. Subclasses of FilterInputStream may further override some of these methods as well as provide additional methods and fields.
The FilterInputStream class is thus a Decorator that can be wrapped around any input stream class. It is essentially an abstract class that doesn’t do any processing, but provides a layer where the relevant methods have been duplicated. It normally forwards these method calls to the enclosed parent stream class.
The interesting classes derived from FilterInputStream include:-
BufferedInputStream: Adds buffering to stream so that every call does not cause I/O to occur.
CheckedInputStream: Maintains a checksum of bytes as they are read
DataInputStream: Reads primitive types (Long, Boolean, Float, etc.) from the input stream.
DigestInputStream: Computes a MessageDigest of any input stream.
InflaterInputStream: Implements methods for uncompressing data.
PushbackInputStream: Provides a buffer where data can be “unread,” if during parsing you discover you need to back up.
These decorators can be nested, so that a pushback, buffered input stream is quite possible.
Decorators, Adapters and Composites
There is an essential similarity among these classes that you may have recognized.
Adapters also seem to “decorate” an existing class. However, their function is to change the interface of one or more classes to one that is more convenient for a particular program.
Decorators add methods to particular instances of classes, rather than to all of them. You could also imagine that a composite consisting of a single item is essentially a decorator.
Once again, however, the intent is different
Consequences of the Decorator Pattern
The Decorator pattern provides a more flexible way to add responsibilities to a class than by using inheritance, since it can add these responsibilities to selected instances of the class. It also allows you to customize a class without creating subclasses high in the inheritance hierarchy. Design Patterns points out two disadvantages of the Decorator pattern One is that a Decorator and its enclosed component are not identical. Thus tests for object type will fail. The second is that Decorators can lead to a system with “lots of little objects” that all look alike to the programmer trying to maintain the code. This can be a maintenance headache.
Decorator and Façade evoke similar images in building architecture,but in design pattern terminology, the Façade is a way of hiding a complex system inside a simpler interface, while Decorator adds function by wrapping a class.
Facade Pattern
The Façade pattern allows you to simplify complexity by providing a simplified interface to subsystems. This simplification may in some cases reduce the flexibility of the underlying classes, but usually provides all the function needed for all but the most sophisticated users. These users can still, of course, access the underlying classes and methods.
FlyWeight Pattern
There are cases in programming where it seems that you need to generate a very large number of small class instances to represent data. Sometimes you can greatly reduce the number of different classes that you need to instantiate if you can recognize that the instances are fundamentally the same except for a few parameters. If you can move those variables outside the class instance and pass them in as part of a method call, the number of separate instances can be greatly reduced.
The Flyweight design pattern provides an approach for handling such classes. It refers to the instance’s intrinsic data that makes the instance unique, and the extrinsic data which is passed in as arguments. The Flyweight is appropriate for small, fine-grained classes like individual characters or icons on the screen. For example, if you are drawing a series of icons on the screen in a folder window, where each represents a person or data file, it does not make sense to have an individual class instance for each of them that remembers the person’s name and the icon’s screen position. Typically these icons are one of a few similar images and the position where they are drawn is calculated dynamically based on the window’s size in any case.
In another example in Design Patterns, each character in a font is represented as a single instance of a character class, but the positions where the characters are drawn on the screen are kept as external data so that there needs to be only one instance of each character, rather than one for each appearance of that character.
Sharable Objects
The Smalltalk Companion points out that sharable objects are much like Flyweights, although the purpose is somewhat different. When you have a very large object containing a lot of complex data, such as tables or bitmaps, you would want to minimize the number of instances of that object. Instead, in such cases, you’d return one instance to every part of the program that asked for it and avoid creating other instances.
A problem with such sharable objects occurs when one part of a program wants to change some data in a shared object. You then must decide whether to change the object for all users, prevent any change, or create a new instance with the changed data. If you change the object for every instance, you may have to notify them that the object has changed.
Sharable objects are also useful when you are referring to large data systems outside of Java, such as databases.
Proxy Pattern
The Proxy pattern is used when you need to represent a complex object by a simpler one. If creating an object is expensive in time or computer resources, Proxy allows you to postpone this creation until you need the actual object. A Proxy usually has the same methods as the object it
represents, and once the object is loaded, it passes on the method calls from the Proxy to the actual object.
There are several cases where a Proxy can be useful:
1. If an object, such as a large image, takes a long time to load.
2. If the object is on a remote machine and loading it over the network may be slow, especially during peak network load periods.
3. If the object has limited access rights, the proxy can validate the access permissions for that user.
Proxies can also be used to distinguish between requesting an instance of an object and the actual need to access it. For example, program initialization may set up a number of objects which may not all be used right away. In that case, the proxy can load the real object only when it is needed.
Let’s consider the case of a large image that a program needs to load and display. When the program starts, there must be some indication that an image is to be displayed so that the screen lays out correctly, but the actual image display can be postponed until the image is completely loaded. This is particularly important in programs such as word processors and web browsers that lay out text around the images even before the images are available.
An image proxy can note the image and begin loading it in the background, while drawing a simple rectangle or other symbol to represent the image’s extent on the screen before it appears. The proxy can even delay loading the image at all until it receives a paint request, and only then begin the process.
Comparison with Related Patterns
Both the Adapter and the Proxy constitute a thin layer around an object. However, the Adapter provides a different interface for an object, while the Proxy provides the same interface for the object, but interposes itself where it can save processing effort.
A Decorator also has the same interface as the object it surrounds, but its purpose is to add additional (usually visual) function to the original object. A proxy, by contrast, controls access to the contained class.
0 Comments:
Post a Comment
<< Home