DesignPatterns

From Suhrid.net Wiki
Jump to navigationJump to search

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.

AdapterPattern.png

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.

ProxyPattern.png

  • 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.


BuilderPattern.png

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.

PrototypePattern.png

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.

CommandPattern.png

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.

VisitorPatternUML.png

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.

MetaclassPattern.png

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.