Object-Oriented-Design Experience
High-level: structural models (layered, client-server etc.), control models, pipes & filters.
Idioms: getters/setters, while(&dst++ = *src++) etc.
What comes in between? OOD wisdom - collections, event loops/callbacks, MVC etc.
Sidenote: Literate Programming: Tangle & Weave
The order in which code makes sense to read is not the same the compiler wants to receive - so weave up the documentation and tangle up the source code.
Talk about different parts of the program from different directions - some things are best described as code, some as text, some as diagrams.
What are Design Patterns
- Distilled wisdom about a specific problem that occurs frequently in OO design
- A reusable design micro-architecture
The core is a simple class diagram with extensive explanation - documentation for an elegant, widely-accepted way of solving a common OO design problem.
Design patterns are:
- Reusable chunks of good design
- Solutions to common problems
- Not a perfect solution, but balances forces
- Essential for OO developers
A definition for ‘Design Pattern’
A solution to a problem in a context
Forces
Correctness: completeness, type-safety, fault-tolerance, security, transactionality, thread safety, robustness, validity, verification etc.
Resources: efficiency, space, ‘on-demand-ness’, fairness, equilibrium, stability etc.
Structure: modularity, encapsulation, coupling, independence, extensibility, reusability, context-dependence, interoperability etc.
Construction: understandability, minimality, simplicity, elegance, error-proneness, etc.
Usage: ethics, adaptability, human factors, aesthetics etc.
Resolution of Forces
Impossible to prove a solution is optional; make argument backed up with:
- Empirical evidence for goodness
- Rule of 3: don’t claim something is a pattern until you can point to three independent usages
- Comparisons with other solutions (possibly failed ones too)
- Independent authorship
- Don’t be the second person after the inventor to use it
- Reviews
- By independent domain and pattern experts
Documenting Patterns
- Name
- Intent: a brief synopsis
- Motivation: context of the problem
- Applicability: circumstances under which the pattern applies
- Structure: class diagram of solution
- Participants: explanation of classes/objects and their roles
- Collaboration: explanation of how classes/objects cooperate
- Consequences: impact, benefits, liabilities
- Implementations: techniques, traps, language-dependent issues
- Sample code
- Known uses: well-known systems already using the pattern
Documenting Pattern Instances
- Map each participant in the GoF pattern to its corresponding element
- Interface/abstract
- Concrete
- Association
The Gang of Four
The design patterns book authored by Gamma, Helm, Johnson and Vlissides containing a catalog of 23 design patterns, each being a creational, structural or behavioral pattern.
Creational Patterns
Abstract Factory (AKA Kit)
Interface for creating families of related/dependent objects without specifying their concrete classes.
e.g. when making UI elements, want scrollbars, buttons etc. to all use the same theme.
public abstract AbstractFactory {
public AbstractProductA createProductA();
public AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProduct1A();
}
...
}
Builder
Separates construction of a complex object from its representation so that the same construction process can create different representations.
Factory Method (AKA Virtual Constructor)
Define interface for creating an object and let subclasses decide which concrete class to instantiate - allows instantiation to be deferred to its subclasses.
Problem: code that expects an object of a particular class doesn’t need to know which subclass the object belongs to (as long as it follows the LSP).
The exception to this rule is when creating a new object - you must know its exact class. Hence, ‘new’ is glue.
Broken polymorphism:
if (isWizard()) weapon = new Wand()
else if (isFighter()) weapon = new Sword();
Solution:
- Move the ‘new’ method into an abstract method
- Override that method to create the right subclass object
public abstract Creator {
public abstract Product factoryMethod();
}
public class ConcreteCreator implements Creator {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
Creator
factoryMethod()
doSomething()
△
|
|
ConcreteCreator
factoryMethod()
Note that it is common to have more than one factory method.
Parameterized factory methods can produce more than one type of product, add constraints/details etc.
public makeWeapon(Type type) {
if (type == Weapons.DAGGER) return new Dagger();
...
}
public makeWeapon(Owner self) {
if (self.height() > 180) return BigDagger();
...
}
Prototype
Specify the kinds of objects to create using a prototypical instance, creating new objects by copying the prototype.
Singleton
Intent: ensure class only has one instance and provide a global point of access to it.
Problem:
- Some classes should only hve one instance
- How can we ensure someone doesn’t construct another
- How should other code find that one instance?
Solution:
- Make the constructor private
- Use a static attribute in the class to hold the one instance
- Add a static getter for the instance
<<singleton>>
Singleton
private $uniqueInstance
public $getInstance()
private Singleton()
(NB: $ means static)
Use lazy initialization approach in the getInstance method.
Issues:
- Sub-classing the singleton is possible but difficult; may not be worth using this pattern
- Thread safety -
getInstanceshould be synchronized anduniqueInstancebe made volatile
Structural Patterns
Adapter
Converts interface of a class into an interface the client expects - allows classes with incompatible interfaces to work together.
Bridge
Decouple an abstraction from its implementation so they can be varied independently of each other.
Composite
Problem: objects contain other objects to form a tree, but want client code to be able to treat composite and atomic objects uniformly.
e.g. Person has eat(FoodItem) method where Bread etc. is a FoodItem. Meal is composed of multiple FoodItems so a new method, eat(Meal) is required.
Solution: create abstract superclass that represents both composite and atomic objects.
Used in Swing’s JComponent.
public class Client {
private Component component;
}
public abstract Component {
public doSomething();
/*
These methods are sometimes defined in Composite
*/
public add(Component);
public remove(Component);
public getChild(int);
}
public class Leaf {
public doSomething();
public add(Component component) {
throw new NotImplementedException();
// Or:
return false;
}
...
}
abstract class Composite extends Component {
private List<Component> components;
public doSomething() {
for(Component component: components) {
component.doSomething();
}
}
public add(Component component) {
components.add(component);
}
public remove(Component component) {
components.remove(component);
}
public getChild(int index) {
return component.get(index);
}
}
Notes:
- GoF has
addetc. methods being abstract; Head First book has them being concrete withNotImplementedexceptions - Easy to add new components
- Common for child to know its parent
- Can make containment too general
Decorator
Attaching additional responsibilities to an object dynamically - an alternative to subclassing for extending functionality. It allows you to extend the existing functionality but not add new public methods.
You can only have one subclass at a time (C++'s multiple inheritance leads to hell) but you can have multiple decorators at the same time: composition over inheritance.
Solution: use aggregation instead of subclassing.
abstract Component {
public void doSomething();
}
public class ConcreteComponent extends Component {
public void doSomething() {
...
}
}
public class Decorator extends Component {
protected Component component;
public void doSomething() {
component.doSomething();
}
}
public class ConcreteDecoratorA extends Decorator {
public void doSomething() {
super.doSomething();
addedBehavior();
}
private void addedBehavior();
}
public class ConcreteDecoratorB extends Decorator {
private State addedState;
public void doSomething() {
super.doSomething();
}
}
Example: Swing’s JScrollPane can be attached to any pane.
Notes:
- Concrete decorators knows the component it decorates
- Business rules: e.g. number, order of decorations
Façade
Providing an unified interface to a set of interfaces in a subsystem - a higher-level interface to make the subsystem easier to use.
Flyweight
Using sharing to support large numbers of fine-grained objects efficiently.
Proxy
Providing a surrogate/placeholder for another object to control access to it.
Behavioral Patterns
Chain of Responsibility
Avoid coupling the sender of a request to its receiver by allowing more than one object to handle the request - chain receiving objects and pass the request along the chain until an object handles it. (e.g. errors bubble up until it gets handled).
Command (AKA Action, Transaction)
Intent: encapsulate the request as an object to
- Parametrize clients with different requests
- Queue/log requests
- Support undoable operations
Participants:
Command:- Declares interface for executing the operation
ConcreteCommand:- Implements
execute - Defines binding between receiver object and action
- Implements
Client- Creates
ConcreteCommandand sets receiver
- Creates
Invoker:- e.g. Button or some other UI element
- Asks the command to carry out the request
Receiver:- Knows how to carry out the request (the target of the command)
// e.g. remote control
public class Invoker {
private Command command;
constructor(Command command) {
// e.g. light toggle command
this.command = command;
command.execute();
}
}
}
public abstract Command {
void execute();
void unexecute();
}
public ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(receiver) {
this.receiver = receiver;
}
public void execute() {
receiver.action();
};
public void unexecute() {};
}
public class Receiver() {
// e.g. smart light switch
public void action();
}
public class Client {
private Receiver receiver;
private ConcreteCommand command;
Client() {
receiver = new Receiver();
command = new ConcreteCommand(receiver);
}
}
From reading Refactoring Guru’s Command pattern page:
// Receiver: where the business logic lives
class Data {
}
// Client: configures commands, passes them to invokers
class App {
constructor() {
this._data = new Data();
this._history = [];
this._stuffCommand = new deleteCommand(this._data);
this._stuffButton = new Button("Delete", () => this._execute(this._stuffCommand))
document.body.appendChild(this._stuffButton);
}
_execute(command) {
command.execute();
this._history.push(command);
}
_unexecute() {
if (this._history.length) this._history.pop().unexecute();
}
}
// ConcreteCommand: command which calls business logic
class StuffCommand {
constructor(data) {
// Gets data in the constructor or on its own
this._data = data;
this._rand = Math.random();
}
execute() {
// Put command in queue, etc.
this._snapshot = this.data.snapshot();
this._data.doStuff(this._rand);
}
unexecute() {
// Generic so can be done in app, but more efficient method will need to know what changed
// and how to undo it, so must be within the command
this._data.restore(this._snapshot);
}
}
// Invoker
class Button {
constructor(text, command) {
this.text = text;
this.command = command;
this._button = document.createElement("button");
this._button.addEventListener("click", () => this.command.execute());
}
}
Interpreter
Given a language, define a representation for its grammar and an interpreter that uses this to interpret sentences.
Iterator (AKA Cursor)
Problem:
- Sequentially access elements of a collection without exposing implementation
- Allows for different types of traversals (e.g. ordering, filtering)
- Allow multiple traversals at the same time
Solution:
- Move responsibility for traversal from collection to an Iterator object, which stores the current position and traversal mechanism
- The collection creates an appropriate iterator
public interface Collection {
public Iterator createIterator();
}
public interface Iterator {
public Element first();
public Element next();
public boolean isDone();
public Element currentItem();
}
public class ConcreteCollection implements Collection {
public ConcreteIterator createIterator() {
}
}
public ConcreteIterator implements Iterator {
...
}
<<interface>> <<interface>>
Collection Iterator
createIterator() -------> first()
△ next()
| isDone()
| currentItem()
| △
| |
ConcreteCollection ConcreteIterator
The set iterator in Java does not have a first method as there is no guaranteed ordering.
for(Collectable c: someCollection) {
}
// Implicitly does:
Collectable c;
Iterator<Collectable> iterator = someCollection.iterator();
while(iterator.hasNext()) {
c = iterator.next();
// But the explicit version can also do
if (c.val == 10) {
iterator.remove();
}
}
Mediator
Define an object that encapsulates how a set of objects interact with each other - reduces coupling by keeping objects from referring to each other explicitly.
Memento
Capture and externalize an object’s internal state (without violating encapsulation) so that it can be restored to this state.
Observer (AKA Publish-Subscribe, Dependents)
One-to-many dependency between objects so that all dependents are notified when an object changes state.
Problem: separate concerns into different classes while avoiding tight coupling and keeping them in sync (e.g. separating GUI code from model).
Solution:
- Separate into Subjects and Observers
- The Subject knows which objects are observing it but nothing else
- When the Subject changes, all Observers are notified
Subject Observer
attach(Observer) 0..*
detach(Observer) -----------> update(Observable, Object)
notify()
△ △
| |
| 1 |
ConcreteSubject <------------ ConcreteObserver
doSomething() subject update()
doSomething will call notify
In Java:
- Subject is class called
ObservablewhileObserveris an interface - Depreciated
- Adds a ‘dirty’ flag to avoid notifications at the wrong time (e.g. transactions in progress?)
JavaFX introduced java.beans.PropertyChangeSupport:
private pcs = new PropertyChangeSupport(this);
addListener(PropertyChangeListener)
removeListener(PropertyChangeListener)
pcs.firePropertyChange(String, oldVal, newVal)
class Observable {
private PropertyChangeSupport pcs;
public Observable() {
pcs = new PropertyChangeSupport(this);
}
addListener(PropertyChangeListener pcl) {
pcs.addPropertyChangeListener(pcl);
}
removeListener(PropertyChangeListener pcl) {
pcs.removePropertyChangeListener(pcl);
}
doSomething() {
pcs.firePropertyChange(name, oldVal, newVal);
}
}
class Observer implements PropertyChangeListener {
public propertyChange(PropertyChangeEvent pce) {
}
}
Notes:
- Changes are broadcast to all Observers; each needs to decide if it cares about a particular change
- Observers don’t know about each other; complex dependencies and cycles are possible
- Observers aren’t told what changed
- Figuring out what changed can take a lot of work and may require them to retain a lot of the Subject’s state
- A variant allows the update method to contain details about what changed - more efficient, but tighter coupling
- The subject should only call
notifywhen it is in a consistent state (i.e. transaction has ended)- Beware subclasses calling base class methods that call
notify()
- Beware subclasses calling base class methods that call
State
Intent: allow objects to alter behavior when their internal state changes - the object appears to have changed class.
TL;DR treat objects as FSMs that change their behavior depending on state.
Need a state chart representation of object behavior e.g.
admin approved
o draft <-------------------
^ expired |
| submit/ v
| review failed published
v ^
review ___________________|
admin reviewed
When a new stage gets added, what happens to existing object?
The pattern:
- State-specific behavior encapsulated in concrete classes
- State interface declares one or more state-specific methods - all methods should make sense for all concrete states
- Context communicates via the state interface
Implementation:
- The context and concrete states can perform state transitions by replacing the context’s state object
- If transition criteria are fixed then it can be done in the context - this allows concrete state classes to be independent
- Initial state is set in the context
- Explicit transitions prevents getting into inconsistent states
public abstract State {
void handle1();
void handle2();
}
public class ConcreteStateA extends State {
private Context context;
// May possibly be in the abstract class
public void setContext(Context context) {
this.context = context;
}
public void handle1() {
State state = new ConcreteStateB();
context.changeState(state);
}
public void handle2() { }
}
public class ConcreteStateB extends State {
public void handle1() { }
public void handle2() { }
}
public class Context {
State state;
public Context(State initialState) {
state = initialState;
}
public void changeState(State state) {
this.state = state;
}
public void request1() {
...
state.handle1();
};
public void request2() {
...
state.handle2();
}
}
Strategy (AKA Policy)
Intent: define a family of algorithms, encapsulating each one and making them interchangeable - Strategy lets the algorithm vary independently from the client that uses it.
Problem: change an object’s algorithm dynamically, rather than through inheritance.
Solution: move the algorithms into their own class hierarchy (composition over inheritance).
Used by AWT/Swing layout managers.
public abstract Strategy {
protected Context context;
public Result algorithm();
}
public class ConcreteStrategyA extends Strategy {
public Result algorithm() {
...
}
}
...
Notes:
- Context needs to know what strategies exist so it can pick one
- Strategies need to access relevant context data
Template Method
Define the skeleton of an algorithm, deferring some steps to subclasses - allows subclasses to redefine certain steps of an algorithm without changing its structure.
Problem: implement the algorithm skeleton but not the details
Solution: put the skeleton in an abstract superclass and use subclass operations to provide the details
public abstract AbstractClass {
public final void templateMethod() {
}
abstract protected void primitiveOperation1();
abstract protected void primitiveOperation2();
}
public class ConcreteClass extends AbstractClass {
protected void primitiveOperation1() { }
protected void primitiveOperation2() { }
}
Hooks:
- Can include hooks so that subclasses can hook into the algorithm at suitable points - they are free to ignore the hook
- Hook declared in abstract class with empty or default implementation
e.g. PrepareMeal abstract class may have isTakeAway hook. Subclass could call it during their assemble to change how the meal is assembled depending on if it is an eat-in or takeaway meal.
Visitor
Represent an operation to be performed on elements of an object structure - allows new operations to be defined without changing the classes of the elements it operates on.
Pattern Language
Alexandrian Patterns
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such as way that you can use this solution a million times over, without ever doing it the same way twice
Christopher Alexander, A Pattern Language
A set of interrelated patterns all sharing some of the same context and perhaps classified into categories.
Abstract Factory meets Singleton
class ConcreteFactory extends AbstractFactory {
private ConcreteFactory instance;
public static getInstance() {
if (instance == null) instance = new ConcreteFactory();
return instance;
}
public ConcreteProductA getA() {
return new ConcreteProductA();
}
}
Iterator meets Factory Method
interface AbstractCollection {
/**
* This method is a factory method that returns a product
*/
Iterator iterator();
}
interface Iterator { ... }
public class ConcreteCollection implements AbstractCollection {
ConcreteIterator iterator();
}
public class ConcreteIterator implements Iterator { ... }