DesignPatterns
From Suhrid.net Wiki
Jump to navigationJump to searchContents
Intro
- A design pattern systematically names, explains, and evaluates an important and recurring design problem and its solution.
- They capture the intent behind a design by identifying objects, collaborations, and distributions of responsibilities.
- They capture static and dynamic structures of successful solutions to problems.
- Patterns support reuse of software architecture and design.
- They give software engineers a vocabulary with which to describe their designs.
- Patterns help to improve key quality characteristics, such as reusability, extensibility, and modularity
- Patterns solve design problems such as:
- Finding appropriate classes to solve a problem.
- Determining how abstract or how concrete a class should be.
- Specifying interfaces of classes, architectures, and binary components.
- Specifying implementations.
- Designing for change.
- Patterns are not code-reuse they are experience reuse !
Advantages
- Enables the reuse of software designs.
- Efficiently capture expert knowledge and design tradeoffs.
- Improve developer communication - common vocabulary.
- Improves designs - by learning from others mistakes.
Disadvantages
- They are often deceptively simple and therefore not considered.
- There may be pattern overload - patterns for the sake of patterns.
- Patterns must be validated by experience.
- Using them is a human intensive activity.
Pattern Classification
Creational Patterns
- For initializing and configuring collections of classes and objects.
- e.g Factory, Builder.
Structural Patterns
- Decoupling interface and implementation.
- e.g. Adapter, Facade, Decorator
Behavioural Patterns
- Dynamic interactions among collaborations of objects.
- e.g. Visitor, Memento
Facade
Intent
- Provide a common interface to a set of interfaces within a subsystem.
- Defines a higher level interface to make the subsystem interfaces easier to use.
Motivation
- Provide a simplified interface.
Consequences
- Shield clients from myriad subsystem components - hence promote weak coupling between clients and subsystems.
- Reduces number of objects clients have to deal with.
- Using a facade promotes decoupling between the client and the subsystems.
- Note that clients can still access the subsystems directly.
- Clients make a choice between ease of use and fine grained control (through direct access of subsystems).
Examples
public class Compiler {
public Compiler();
private Node node_tree;
private Scanner scanner;
private Parser parser;
private Risc_CG generator;
public void compile() {
node_tree=parser.parse(scanner);
generator.emit(node_tree);
.....
}
}
- The Compiler class is a facade to various sub system interfaces (Node, Scanner, Parser, Generator) that comprise a compiler.
- The compile() method provides a high level interface to the whole process of compiling using various subsystem interfaces.
- Here's one more example of a HomeTheatreFacade which simplifies the operation of various subsystems of a home theatre.
public class HomeTheaterFacade {
private Amplifier amp;
private Tuner tuner;
private DVDPlayer dvdPlayer;
private Projector proj;
private Lights lights;
private Screen screen;
public void watchMovie() {
lights.dim();
screen.down();
projector.on();
amp.on();
amp.setVolume(5);
dvd.on();
dvd.play();
}
}
Adapter
Intent
- Convert the interface of one class into another interface that clients expect.
- An adapter would let objects work together, that otherwise wouldn't because of incompatible interfaces.
Motivation
- There is a class that provides required functionality, but does not support the interface the design needs.
Example
- The enumeration interface allows us to step through elements of a collection.
- However, there is now a new Iterator interface which has more functionality.
- What if our client's design expect enumeration interfaces, but our data structure e.g. ArrayList doesn't implement Enumerations but implements Iterator.
- We need an adapter to convert the Iterator interface to the Enumeration interface.
Decorator
Intent
- Attach additional responsibilities to an object at run time.
Motivation
- We would like to add extra behavior to an object, not a class. Modifying the class would mean making compile time changes.
- Inheritance is a static mechanism and cannot solve this problem.
Consequences
- Can add and remove properties at run time.
- Can handle an arbitrary number of properties.
Example
- The java io package makes heavy use of decorators.
- For e.g. we have an InputStream as an interface. There is a FileInputStream, StringBufferInputStream etc.
- What if we wanted to filter each input stream and there were different types of filters. e.g. Buffered, Pushback, LineNumber, LowerCaseInput etc
- Then there would be a class explosion FileInputBuffered, FileInputPushback, StringBufferLineNumber etc....
- So we'll decorate InputStream's with a FilterInputStream decorator.
- InputStream is the abstract component, FilterInputStream is a decorator, then the different types of filters Buffered,Pushback are concrete decorators.
- Here's a LowerCaseInputStream decorator
public class LowerCaseInputStream extends FilterInputStream {
protected LowerCaseInputStream(InputStream in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
return ( c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for(int i=offset; i < offset + result; i++) {
b[i] = (byte) Character.toLowerCase((char)b[i]);
}
return result;
}
}
- Here's the test code, notice how the InputStream in is being decorated with different behaviours at run-time using Composition.
public class InputTest {
public static void main(String[] args) {
int c;
try {
InputStream in = new LowerCaseInputStream(new BufferedInputStream(new StringBufferInputStream("Abc Def Xyz")));
while((c = in.read()) > 0) {
System.out.print((char) c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Composite
Intent
- Compose objects into tree structures to represent part whole hierarchies.
- Allows clients to treat individual objects and compositions of objects uniformly.
Motivations
- Drawing tools - Treat Elements and Groups of Elements in the same way. e.g. draw(), drag(), can be performed on elements as well as group of elements.
- File Systems - Files and Folders e.g. move(), copy() etc.
- Text Systems - Words, Sentences and Pragraphs. e.g. delete(), insert() etc.
Example
- Consider a restaurant Menu. A menu has a MenuItem e.g. a dish. A menu can also have sub-menus e.g. DessertMenu which can have Dessert Menu Items.
- If we want to print a Menu, we can treat Menu and MenuItems as a single composite structure.
public abstract class MenuComponent {
public void print() {
throws new UnsupportedOperationException();
}
}
public class MenuItem extends MenuComponent {
public void print() {
print(name);
print(price);
print(description);
}
}
public class Menu extends MenuComponent {
List<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
public void print() {
for(MenuComponent mc : menuComponents) {
mc.print(); //This works recursively, if a MenuComponent is a menu.
}
}
}
Proxy
Intent
- To provide a surrogate or a placeholder for another object to control access to it (the another object).
Motivation
- A target object can exist in another address space or hard to access place. By providing a proxy for the object - it can stand in for the real object and make it transparent for the client.
- The target object might be expensive to create or may take a long time. The proxy can stand in for the target until it is ready and then defer all calls to the target object.
Example
- The classic example is Java RMI.
- The client calls a method on a local RMI Proxy object that forwards the call across the network to a remote object that does the actual work.
- So the remote proxy controls access to a remote object.
- Both the proxy and realsubject implement the subject interface, therefore clients can treat proxy just like RealSubject.
- The proxy keeps a reference to the RealSubject, so that it can forward requests to the Subject when necessary.
Singleton
Intent
- Ensure that the class has only one instance and provide a global access point for it.
- Why not just use statics ? Object semantics may be required, plus also singletons can be created whenever we want, whereas statics tend to get loaded whether we use them or not.
Motivation
- There are some things which you need only one instance of. e.g. ImmortalMemory in Real time Java, or maybe a system wide scheduler object.
Example
- See below example, the Singleton class has only one unique instance.
- It also provides a global access point to the instance through the getInstance() method.
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if(uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
Builder
Intent
- Separate construction of a complex object from its representation.
- One construction process can be used for several representations.
Motivation
- A reader for XML documents should be able to convert the read contents to some other representation.
- Need to encapsulate a way a complex object is constructed from the client, hides internal representation of the product (e.g. PostScript) from the client.
- Allows objects to be constructed in a multistep fashion.
Example
- In the below example, the client XMLReader has a reference to TextBuilder, an abstract builder
- This can have many implementations e.g. PostScriptBuilder, UnicodeBuilder etc.
- The client, XMLReader then calls build_char(),build_word() on the builder which then builds the output.
- The XMLReader then does a getOutput() on the builder to retrieve the output.
Prototype
Intent
- Rather using a constructor, create new objects by using a prototypical instance.(via cloning).
- For e.g. creating a new instance might be expensive or complicated.
Motivation
- Need to hide the complexity of creating an object from the client.
Example
- Here the ItemRegistry maintains a basic list of pre-created item objects in a registry.
- When a client requests it to create a basic item, what it does is retrieves the basic object of the item type from the registry, clones it and returns the reference to the client.
Command
Intent
- Encapsulate request on an object, thereby allowing us to parameterize clients with different requests and supporting undoable operations.
- The idea is to encapsulate method invocation.
Motivation
- Decouple the requester of a certain action from the object that actually performs the action.
- Implementing an undo semantic, Addition of new operations on current selection.
Example
- Check the below examples of a method requests to a File. The method operations are encapsulated as a command object, so the invoker doesnt care about who's implementing the command.
- Note how the command pattern makes it easy to implement undo.
Visitor
Intent
- A visitor represents an operation to be performed on the components of a class structure.
- Allow definitions for new operations on the structure, without changing the classes. (only change the visitor instead).
- An effective way to attach new operations to objects without changing the object.
Example
- Here's an example from Wikipedia.
- In this case, the CarElement classes need not be changed when a new operation is added, for e.g. wash(). it is instead implemented in the visitor.
- For e.g. CarElementWashVisitor will implement the methods such as visit(Wheel w), visit(Engine e) and then add some code to wash the wheel, engine etc.
Metaclass
Intent
- Represent a metaclass directly in the class model.
- Allow representation of things and sorts of things.
Motivation
- We need to represent individual objects and the type of objects (their class) as well.
- Many languages dont allow manipulation of inbuilt metaclasses, so we need to create a pattern.
Example
- Here is an example of a metaclass pattern, an Aircraft is a class of which there are instances like G-BWN.
- We also use an AircraftType as a metaclass of Aircraft. Instances of AircraftType are AirCraft classes. Note, there is no generalization between a class and it's metaclass. There is an association.
GRASP Patterns
Intro
- GRASP stands for General Responsibility Assignment Software Patterns.
- These patterns describe some of the fundamental principles of assigning responsibilities to objects - which is one of the hardest things to do in OO.
Information Expert
- Assign responsibility for a certain operation to the class which has the most of necessary information, aka the information expert.
Low coupling
- Assign responsibilities such that the coupling between classes remains low.
- Don't assign responsibilities that will create high dependency between classes and high impact in one class when some other class changes.
High cohesion
- Assign responsibilities such that cohesion stays high.
- For e.g. all printing related responsibilities should stay in a single Printer class.
Creator
- Similar to the Factory pattern.
- An object of class A has to be created.
- Assign class B the responsibility to create objects of class A, if
- B aggregates A objects
- B contains A objects
- B records instances of A objects
- B closely uses A objects
- B has the initializing data that will be passed to A when it is created i.e. B is an expert
- B is already a creator of A objects.
- An example of class A is Employee and class B is Company. So the Company class can have the responsibility to create Employee objects.
Pure Fabrication
- OO designs are characterized by implementing classes that represent concepts in the real world.
- However there are situations in which assigning responsibilities to only real world classes leads to problems in terms of poor cohesion and coupling.
- Therefore pure fabrication: made up classes, which do not exist in the real world are defined.
Indirection Pattern
- This is a fundamental pattern that is used to ensure low coupling between objects.
- The Indirection pattern supports low coupling (and reuse potential) between two elements by assigning the responsibility of mediation between them to an intermediate object.
- An example of this is the introduction of a controller component for mediation between data (model) and its representation (view) in the Model-view-controller pattern.
Polymorphism
- If behavior varies by type (Class), who is responsible ? The type of course.
- So the type is assigned responsibility of the behavior.
- The implementation is done by dynamically bound polymorphic operations - the very core of OO :)
Project specific patterns
- Many projects develop their own patterns to do certain tasks.
- These must be documented in the GoF style.
- This is how we handle:
- Exceptions.
- Logging user actions.