Object-oriented Patterns
This note contains object-oriented design patterns divided into four categories:
- Behavioral Patterns: managing responsibilities between objects.
- Structural Patterns: forming single object by merging multiple objects.
- Creational Patterns: creating new objects.
- Interface Patterns: how objects communicate with each other.
Each pattern has a pattern name, problem it solves, solution for that problem, example in some language and which are possible additional results.
Behavioral Patterns
Chain of Responsibility
Problem: Multiple objects may handle a request. The handler does not have to be a specific object. A request not being handled is an acceptable outcome.
Solution: Delegates request to a chain of processing objects a.k.a. handlers. Gives more than one object an opportunity to handle a request by linking receiving objects together. Only one handler may process the request.
Handler: <interface>
+ handleRequest()
Handler1
+ handleRequest()
Handler2
+ handleRequest()
Handler3
+ handleRequest()
Command
Problem: Requests need to be handled at a variable time, in a variable order, command executor must be separated from the commander or a history of the requests is needed.
Solution: Encapsulate a request or function call as a passable object. Allows queuing and callbacks of requests.
var CarManager = {
// Request information.
requestInfo: function (model, id) {
return 'The purchase info for ' + model
+ ' with ID ' + id + ' is being processed...';
},
// Purchase the car
buyVehicle: function (model, id) {
return 'You have successfully purchased Item ' + id
+ ', a ' + model + '.';
}
};
CarManager.execute = function (commad) {
return CarManager[commad.request](commad.model, commad.carId);
};
var actionA = CarManager.execute({
request: 'requestInfo',
model: 'Ford Mondeo',
carId: '543434'
});
console.log(actionA);
// The purchase info for Ford Mondeo with ID 543434 is being processed...
var actionB = CarManager.execute({
request: 'buyVehicle',
model: 'Ford Mondeo',
carId: '543434'
});
console.log(actionB);
// You have successfully purchased Item 543434, a Ford Mondeo.
Interpreter
Problem: There is simple grammar to interpret. Decoupling grammar from underlying expressions is desired.
Solution: Defines a representation for a grammar as well as a mechanism to understand and act upon the grammar.
Interpreter: <interface>
+ interpret(string expression) Result
Interpreter1
+ interpret(string expression) Result
Interpreter2
+ interpret(string expression) Result
Iterator
Problem: A uniform interface for traversal is needed. Multiple or concurrent traversals of the elements are needed.
Solutions: Allow access to an object without showing the real representation. Wrap a data structure to object for transparent traversal.
var agg = (function () {
var index = 0;
var data = [1, 2, 3, 4, 5];
var length = data.length;
return {
next: function () {
var element;
if ( ! this.hasNext() ) {
return null;
}
element = data[index];
index = index + 2;
return element;
},
hasNext: function () {
return index < length;
},
rewind: function () {
index = 0;
},
current: function () {
return data[index];
}
};
}());
while ( agg.hasNext() ) {
console.log( agg.next() );
}
// => 1, 3, 5
// go back
agg.rewind();
console.log ( agg.current() );
// => 1
Mediator
Problem: Classes too tightly coupled. Too many relationships exist and common point of communication is needed.
Solution: Allows loose coupling between classes by being the only class that has detailed knowledge of their methods.
function Player(name) {
this.points = 0;
this.name = name;
}
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
var scoreboard = {
// HTML element to be updated.
element: document.getElementById('results'),
// Update the score display.
update: function (score) {
var msg = '';
for (var i in score) {
if ( score.hasOwnProperty(i) ) {
msg += '<p><strong>' + i + '<\/strong>: ';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
};
var mediator = {
// All the players
players: {},
// Initialization
setup: function () {
var players = this.players;
players.home = new Player('Home');
players.guest = new Player('Guest');
},
// someone plays, update the score
played: function () {
var players = this.players;
var score = {
Home: players.home.points,
Guest: players.guest.points
};
scoreboard.update(score);
},
// handle user interactions
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // key "1"
mediator.players.home.play();
return;
}
if (e.which === 48) { // key "0"
mediator.players.guest.play();
return;
}
}
};
// go!
mediator.setup();
window.onkeypress = mediator.keypress;
// game over in 30 seconds
setTimeout(function () {
window.onkeypress = null;
console.log('Game over!');
}, 30000);
Memento
Problem: The internal state of an object must be saved and restored at a later time.
Solutions: Allow for capturing and externalizing an object's internal state so that it can be restored later. Without violating encapsulation.
Originator
- State
+ setMemento(memento m)
+ createMemento() memento
Memento
- State
Undo functionality is the most common example.
Observer
Problem: I have object and I need to operate on a whole group of objects when that object changes.
Solution: Allow objects (observers) to attach themselves to a single object (subject). When the subject changes, it notifies all listed observers.
interface Observer {
public function update(Subject $subject) {};
}
interface Subject {
public function attach(Observer $observer) {};
public function detach(Observer $observer) {};
public function notify() {};
}
class Box implements Subject {
private $storage;
public function __construct() {
$this->storage = new Storage();
}
public function attach(Observer $observer) {
$this->storage->attach($observer);
}
public function detach(Observer $observer) {
$this->storage->detach($observer);
}
public function notify() {
foreach ($this->storage as $observer) {
$observer->update($this);
}
}
}
class Shopkeeper implements Observer {
private $box;
public function __construct(Box $box) {
$this->box = $box;
}
public function update(Subject $subject) {
// Box changed, do something.
}
}
// Observer pattern can be implemented in various ways:
var invokeMe = function(arg1, arg2, arg3) {
console.log('Hello!');
};
// Observer: Event Emitter
myObject.on('myEvent', invokeMe);
myObject.trigger(new Event('myEvent', param1, param2, param3));
myObject.off('myEvent', invokeMe);
// Observer: Publish-Subscribe
globalBroadcaster.subscribe('myEvent', invokeMe);
globalBroadcaster.publish('myEvent', param1, param2, param3);
globalBroadcaster.unsubscribe('myEvent', invokeMe);
// Observer: Signals
myObject.myEvent.add(invokeMe);
myObject.myEvent.dispatch(param1, param2, param3);
myObject.myEvent.remove(invokeMe);
State
Problem: The behavior of an object should be influenced by its current condition. Complex conditions tie object behavior to its state.
Solutions: Allow the object to behave in different ways based upon its internal state.
Email object can be sent but then it cannot be `send()` again.
State: <interface>
+ handle(request r)
State1:
+ handle(request r)
State2:
+ handle(request r)
Strategy / Dependency Injection
Problem: Multiple variations of a functionality is required. Multiple inheritance could beused. The behavior of a class should be defined at run-time.
Solution: Encapsulate behaviour to an object, pass it at construction and delegate the behaviour.
// Strategy
class Lesson {
private $duration;
private $costStrategy;
// CostStrategy is an interface.
public function __construct($duration, CostStrategy $strategy) {
$this->duration = $duration;
$this->costStrategy = $strategy;
}
public function cost() {
return $this->costStrategy->cost($this);
}
}
// But normal Strategy-pattern causes hard dependencies in the code.
Notifier notifier = new Notifier(emailSender, databaseConnection);
// Some people prefer hiding the construction so it becomes
// Dependency Injection -pattern. Strategies that you can change at run-time.
Notifier notifier = Container.Get<Notifier>();
Template Method
Problem: A single abstract implementation of an algorithm is needed. Most or all subclasses need to implement the behavior.
Solution: Subclasses decide how to implement an algorithm.
You have InstantMessage, InstantVideoMessage and InstantTextMessage.
Both message types implement serialization differently.
AbstractClass
+ templateMethodThatUsesSubMethod()
# subMethod()
ConcreteClass
+ subMethod()
Visitor
Problem: An object structure must have many unrelated operations performed upon it.
Solution: Allow one or more operations to be applied to a set of objects at run-time, decoupling the operations from the object structure.
You have a collection of invoices and need to calculate your taxes.
You are now in region X and use rate XX, specified by visitor XXX.
When you move to region Y which uses rate YY, you change visitor to YYY.
Visitor: <interface>
+ visit(ElementA a)
+ visit(ElementB b)
Visitor1
+ visit(ElementA a)
+ visit(ElementB b)
Element: <interface>
+ accept(Visitor v)
ElementA
+ accept(Visitor v)
ElementB
+ accept(Visitor v)
Structural Patterns
Adapter
Problem: You have two classes that could use each other but do not implement the same interface.
Solution: Permit classes with different interfaces to work together by creating a common object by which they may communicate and interact. Wrap first object with a third object to change the interface.
A billing application and human resources application need to share
information like SSN but implement different interface.
You can create an adapter in between to make the work together.
Bridge
Problem: Abstractions and implementations should not be bound at compile time. Implementation details should be hidden from the client.
Solution: Define an abstract object structure independently of the implementation object structure in order to limit coupling.
Abstraction
+ operation
Implementor: <interface>
+ operationImplementation()
Implementor1
+ operationImplementation()
Implementor1
+ operationImplementation()
Composite
Problem: Hierarchical representations of objects are needed. Objects and compositions of objects should be treated uniformly.
Solution: Use same interface for collection of objects and for single objects. Each object can be treated independently or as a set of nested objects through the same interface.
Shopping cart can have one or multiple items but the cost should be
calculated the same.
Component
+ operation()
+ add(Component c)
+ remove(Component c)
+ getAllComponents() Component[]
Decorator
Problem: Object responsibilities and behaviors should be dynamically modifiable. Subclassing to achieve modification is impractical or impossible.
Solution: Dynamically wrap objects to add or override behaviour in an existing method of an object.
You send an email. In some email clients it get decorated with your
personal information.
Facade
Problem: You have multiple complex classes that need to be controlled but you usually do not need all the functionality they provide.
Solution: Wrap complex interface to a simplified interface.
var mobileEvent = {
stop: function(e) {
e.preventDefault();
e.stopPropagation();
console.log('Stopped mobile event.');
}
};
Flyweight
Problem: Many same kind of objects are used and storage cost is high. The identity of each object does not matter. A few shared objects can replace many unshared ones.
Solution: Facilitate the reuse of many fine grained objects, making the utilization of large numbers of objects more efficient.
You have UI that user can change.
It has DefaultTextArea and CustomTextArea.
All users that do not have custom text area stored use the same
default text area object.
Flyweight: <interface>
+ operation()
SharedState:
- allState
+ operation()
UnsharedState:
- myState
+ operation()
Proxy
Problem: The object being represented is external to the system. You have a class you want to be lazy loaded a.k.a. created on demand. Access control for the original object is required. Added functionality is required when an object is accessed.
Solution: Wrap and object to control access to it. Construct inner object on access.
Subject: <interface>
+ request()
SubjectProxy
+ request()
SubjectReal
+ request()
Creational Patterns
Abstract Factory
Problem: The creation of objects should be independent of the system utilizing them. Systems should be capable of using multiple families of objects. Families of objects must be used together.
Solution: Provide an interface that delegates creation calls to one or more concrete classes in order to deliver specific objects.
Factory: <interface>
+ createElementForX() Element
+ createElementForY() Element
Factory1:
+ createElementForX() ElementA
+ createElementForY() ElementB
Element: <interface>
ElementA
ElementB
Builder
Problem: Object creation algorithms should be decoupled from the system. Run-time control over the creation process is required. The addition of new creation functionality without changing the core code is necessary.
Solution: Separate the construction of an object from its usage.
Director
+ construct()
Builder: <interface>
+ buildMe()
Builder1 <interface>
+ buildMe()
+ myMethod()
Builder2 <interface>
+ buildMe()
+ somethingDifferent()
Factory
Problem: A class will not know what classes it will be required to create. Subclasses may specify what objects should be created. Parent classes wish to defer creation to their subclasses.
Solution: Exposes a method for creating objects, allowing subclasses to control the actual creation process.
Factory: <interface>
+ create() Product
Factory1
+ create() ProductA
Product: <interface>
ProductA
Prototype
Problem: You are creating computationally taxing copies or great number of copies. Classes to be created are specified at run-time. A limited number of state combinations exist in an object.
Solution: Create new objects by cloning existing objects, which contain methods for the container object.
Prototype: <interface>
+ clone() Prototype
+ method()
Prototype1
+ clone() Prototype1
+ method()
Prototype2
+ clone() Prototype2
+ method()
Singleton
Problem: Functionality of a single class is used all around the program. Exactly one instance of a class is required. Controlled access to a single object is necessary.
Solution: Ensure that only one instance of a class is allowed within a system. Hide constructor, provide static method and create on call if does not exist.
Singleton
- static Singleton instance
+ static instance() Singleton
+ method()
Interface Patterns
Argument Object
Problem: How to simplify complex interface that has regular structure?
Solution: Make arguments object to capture common parts of the interface.
view.drawRectangle( from, to, color )
view.fillRectangle( from, to, color )
view.drawOval( from, to, color, flatness )
view.drawRectangle( Graphic graphic )
view.fillRectangle( Graphic graphic )
view.drawOval( Graphic graphic, flatness )
// Where graphic contains variables for `form`, `to` and `color`.
Result: Tradeoff between function with 3 simple objects to a function with 1 complex objects. Decreases coupling between communicating functions because they get coupled to the single more complex object. Complex objects are harder to write.
Selector Object
Problem: How to simplify interface where several function calls just differ in their names?
Solution: Make single message taking an object representing the message selector as an extra argument.
view.drawRectangle( Graphic graphic )
view.fillRectangle( Graphic graphic )
view.drawOval( Graphic graphic )
// Each of these draws on our view.
// Apply selector object pattern by introducing message selector.
view.draw( Graphic graphic )
// Where graphic contains variables for:
// `type` e.g. outlineRectangle, fillRectangle, outlineOval
// `form`
// `to`
// `color`
Result: Trade off between N functions with N similar argument objects to 1 function with N slightly more complex argument objects. This is same kind as Command pattern.
Currier Object
Problem: How to simplify an extremely complicated interface?
Solution: Send simpler messages to a currier object which elaborates them within its context.
view.drawString( String text, Position at, Integer fontSize, Font font )
// Where position varies with arithmetic progression.
// Simplify with currier object Pen.
pen = view.getPen()
// Pen might be present or created for this call.
// Pen accept also default values on getPen.
// Pen( startingPoint, fontSize, font )
// pen = new Pen( new Position(0, 0), 12, 'Times New Roman' )
pen.drawString('This is an example ')
pen.drawString('to illustrate currier objects.')
pen.setFontSize(14)
Result: Currier objects displace the receiver of the protocol, messages sent to a currier object are executed by the original object.
Result Object
Problem: How can you manage a difficult answer to a difficult question?
Solution: Make a result object the whole answer to the question. Starts computation and gives result as soon as it is created.
myCalculator = new MetricCalculator()
interfaceCount = myCalculator.computeSizeOfInterface()
inheritedCount = myCalculator.computeNumberOfInheritedMethods()
overrideCount = myCalculator.computeNumberOfOverriddenMethods()
// Simplify with result object
myCalculator = new MetricCalculator()
myReport = myCalculator.computeMetrics()
// Where myReport is MetricReport object with...
// sizeOfInterface, numberOfInheritedMethods, numberOfOverriddenMethods
Result: Result object can reduce interface size and complexity by providing a single connection point. Although only should be used when resulting collection of results belong to same conceptual domain.
Future Object
Problem: How can you answer a question while you think about something else?
Solution: Make a future object which computes the answer in parallel. Starts computation right away, but gives result when first accessed.
// MetricReport can be constructed so that it start a second thread
// working as a proxy for a object that has not been computed yet.
myCalculator = new MetricCalculator()
myReport = myCalculator.computeMetrics()
// Where myReport is MetricReport, wrapper for values...
// sizeOfInterface, numberOfInheritedMethods, numberOfOverriddenMethods
// You cannot access returned object's values just yet...
// But you can do other unrelated computation.
// This call will force a wait for the future object to become ready.
myReport.sizeOfInterface()
Result: Improves concurrency. Similar benefits as result objects. Has larger overhead than normal result object because of the concurrency. Program becomes less predictable and harder to debug.
Lazy Object
Problem: How can you answer to a question that might never be asked?
Solution: Make a lazy object which can answer the question later, if necessary. Starts computation and gives result after first accessed.
// MetricReport can be constructed so that it start a second thread
// working as a proxy for a object that will compute the result
// when asked.
myCalculator = new MetricCalculator()
myReport = myCalculator.computeMetrics()
// Where myReport is MetricReport, wrapper for values...
// sizeOfInterface, numberOfInheritedMethods, numberOfOverriddenMethods
// You can do other unrelated computation and finally...
myReport.sizeOfInterface()
// This call will force the myReport to start computing itself,
// if it is never called, it will never be computed.
Result: Reduces computational overhead. Harder to determine when the computation takes place and harder to debug.
Sources
- Arguments and Results, James Noble
- Head First Design Patterns, Eric Freeman
- PHP Objects, Patterns and Practice, Matt Zandstra
- DZone Design Patterns
- JavaScript Design Patterns