GoF Behavioral patterns
Definition
Behavioral Design Patterns focus on improving or streamlining the communication between disparate objects in a system.
Chain of Responsibility
Command
Interpreter
Iterator
Template Method
Mediator
Memento
Observer
Visitor
State
Strategy
Chain of responsibility
Chain of responsibility delegates commands to a chain of processing objects.
Example:
class ShoppingCart {
constructor() {
this.products = [];
}
addProduct(p) {
this.products.push(p);
};
}
class NumberDiscount {
constructor() {
this.next = null;
}
setNext(fn) {
this.next = fn;
};
exec(products) {
let result = 0;
if (products.length > 3)
result = 0.05;
return result + this.next.exec(products);
};
}
class PriceDiscount {
constructor() {
this.next = null;
}
setNext(fn) {
this.next = fn;
};
exec(products) {
let result = 0;
let total = products.reduce((a, b) => a + b);
if (total >= 500)
result = 0.1;
return result + this.next.exec(products);
};
}
class NoneDiscount {
exec() {
return 0;
};
}
class Discount {
calc(products) {
let ndiscount = new NumberDiscount();
let pdiscount = new PriceDiscount();
let none = new NoneDiscount();
ndiscount.setNext(pdiscount);
pdiscount.setNext(none);
return ndiscount.exec(products);
};
}
Command
Command creates objects that encapsulate actions and parameters.
In the command pattern, an operation is wrapped as a command object and passed to the invoker object. The invoker object passes the command to the corresponding object, which executes the command.
The command pattern decouples the objects executing the commands from objects issuing the commands. The command pattern encapsulates actions as objects. It maintains a stack of commands whenever a command is executed and pushed to the stack. To undo a command, it will pop the action from the stack and perform the reverse action.
Elements to the Command Pattern:
Receiver - the receivers job is to hold our business logic. When given a command, it knows how to fulfill that request
Command - contains information about the action being called, and its required parameters. It is represented as an object
Executor - the executors job is pass the command to the receiver and call our business logic
Example:
class Cockpit {
constructor(command) {
this.command = command;
}
execute() {
this.command.execute();
}
}
class Turbine {
constructor() {
this.state = false;
}
on() {
this.state = true;
}
off() {
this.state = false;
}
}
class OnCommand {
constructor(turbine) {
this.turbine = turbine;
}
execute() {
this.turbine.on();
}
}
class OffCommand {
constructor(turbine) {
this.turbine = turbine;
}
execute() {
this.turbine.off();
}
}
Example in FE world: Redux implements command pattern (The Store = The Receiver, The Action = The Command, Store Dispatch = Executor).
Interpreter
Interpreter implements a specialized language.
Example:
class Sum {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class Min {
constructor(left, right) {
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() - this.right.interpret();
}
}
class Num {
constructor(val) {
this.val = val;
}
interpret() {
return this.val;
}
}
Iterator
Iterator accesses the elements of an object sequentially without exposing its underlying representation.
Example:
class Iterator {
constructor(el) {
this.index = 0;
this.elements = el;
}
next() {
return this.elements[this.index++];
}
hasNext() {
return this.index < this.elements.length;
}
}
Mediator
Mediator allows loose coupling between classes by being the only class that has detailed knowledge of their methods.
A Mediator is an object that coordinates interactions (logic and behavior) between multiple objects. It makes decisions on when to call which objects, based on the actions (or inaction) of other objects and input. If it appears a system has too many direct relationships between components, it may be time to have a central point of control that components communicate through instead. The Mediator promotes loose coupling by ensuring that instead of components referring to each other explicitly, their interaction is handled through this central point. This can help us decouple systems and improve the potential for component reusability.
Usage
A mediator is best applied when two or more objects have an indirect working relationship, and business logic or workflow needs to dictate the interactions and coordination of these objects. The mediator extracts the workflow from the implementation details and creates a more natural abstraction at a higher level, showing us at a much faster glance what that workflow is. We no longer have to dig into the details of each view in the workflow, to see what the workflow actually is.
Pros and Cons
Pros:
It reduces the communication channels needed between objects or components in a system from many to many to just many to one.
Adding new publishers and subscribers is relatively easy due to the level of decoupling present.
If our modules communicated with each other directly, changes to modules (e.g another module throwing an exception) could easily have a domino effect on the rest of our application. This problem is less of a concern with decoupled systems.
Cons:
It can introduce a single point of failure
Placing a Mediator between modules can also cause a performance hit as they are always communicating indirectly
Because of the nature of loose coupling, it's difficult to establish how a system might react by only looking at the broadcasts
Mediator vs Observer
Mediator and Observer are competing patterns. The difference between them is that Observer distributes communication by introducing "observer" and "subject" objects, whereas a Mediator object encapsulates the communication between other objects. We've found it easier to make reusable Observers and Subjects than to make reusable Mediators.
Mediator vs Facade
The Mediator centralizes communication between modules where it's explicitly referenced by these modules. In a sense this is multidirectional. The Facade however just defines a simpler interface to a module or system but doesn't add any additional functionality. Other modules in the system aren't directly aware of the concept of a facade and could be considered unidirectional.
Example:
class TrafficTower {
constructor() {
this.airplanes = [];
}
requestPositions() {
return this.airplanes.map(airplane => {
return airplane.position;
});
}
}
class Airplane {
constructor(position, trafficTower) {
this.position = position;
this.trafficTower = trafficTower;
this.trafficTower.airplanes.push(this);
}
requestPositions() {
return this.trafficTower.requestPositions();
}
}
Example from the FE world: DOM event bubbling and event delegation. If all subscriptions in a system are made against the document rather than individual nodes, the document effectively serves as a Mediator. Instead of binding to the events of the individual nodes, a higher-level object is given the responsibility of notifying subscribers about interaction events.
Memento
Memento provides the ability to restore an object to its previous state (undo).
Example:
class Memento {
constructor(value) {
this.value = value;
}
}
const originator = {
store: function(val) {
return new Memento(val);
},
restore: function(memento) {
return memento.value;
}
};
class Caretaker {
constructor() {
this.values = [];
}
addMemento(memento) {
this.values.push(memento);
}
getMemento(index) {
return this.values[index];
}
}
Observer
Observer is a publish/subscribe pattern, which allows a number of observer objects to see an event.
The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state. When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic of the notification). When we no longer wish for a particular observer to be notified of changes by the subject they are registered with, the subject can remove them from the list of observers.
The Observer pattern consists of the following components:
Subject: maintains a list of observers, facilitates adding or removing observers
Observer: provides an update interface for objects that need to be notified of a Subject's changes of state
ConcreteSubject: broadcasts notifications to observers on changes of state, stores the state of ConcreteObservers
ConcreteObserver: stores a reference to the ConcreteSubject, implements an update interface for the Observer to ensure state is consistent with the Subject's
Differences Between The Observer And Publish/Subscribe Pattern
The Observer pattern requires that the observer (or object) wishing to receive topic notifications must subscribe this interest to the object firing the event (the subject).
The Publish/Subscribe pattern however uses a topic/event channel that sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application-specific events which can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher.
This differs from the Observer pattern as it allows any subscriber implementing an appropriate event handler to register for and receive topic notifications broadcast by the publisher. The general idea here is the promotion of loose coupling. Rather than single objects calling on the methods of other objects directly, they instead subscribe to a specific task or activity of another object and are notified when it occurs.
Example:
class Fees {
update(product) {
product.price = product.price * 1.2;
}
}
class Proft {
update(product) {
product.price = product.price * 2;
}
}
class Product {
constructor() {
this.price = 0;
this.actions = [];
}
setBasePrice(val) {
this.price = val;
this.notifyAll();
}
register(observer) {
this.actions.push(observer);
}
unregister(observer) {
this.actions = this.actions.filter(el => !(el instanceof observer));
}
notifyAll() {
return this.actions.forEach(el => el.update(this));
}
}
Example of Observer in FE world: 'Connect' from react-redux use Observer pattern under the hood. (The observers are the components, and the subject is the state tree.)
Example of Pub/Sub in FE world: custom events in the browser API.
State
State allows an object to alter its behavior when its internal state changes.
Example:
class OrderStatus {
constructor(name, nextStatus) {
this.name = name;
this.nextStatus = nextStatus;
}
next() {
return new this.nextStatus();
}
}
class WaitingForPayment extends OrderStatus {
constructor() {
super('waitingForPayment', Shipping);
}
}
class Shipping extends OrderStatus {
constructor() {
super('shipping', Delivered);
}
}
class Delivered extends OrderStatus {
constructor() {
super('delivered', Delivered);
}
}
class Order {
constructor() {
this.state = new WaitingForPayment();
}
nextState() {
this.state = this.state.next();
};
}
Strategy
Strategy allows one of a family of algorithms to be selected on the fly at runtime.
Example:
function guestStrategy(amount) {
return amount;
}
function regularStrategy(amount) {
return amount * 0.9;
}
function premiumStrategy(amount) {
return amount * 0.8;
}
class ShoppingCart {
constructor(discount) {
this.discount = discount;
this.amount = 0;
}
checkout() {
return this.discount(this.amount);
}
setAmount(amount) {
this.amount = amount;
}
}
Template method
Template method defines the skeleton of an algorithm as an abstract class, allowing its subclasses to provide concrete behavior.
Example:
class Tax {
calc(value) {
if (value >= 1000)
value = this.overThousand(value);
return this.complementaryFee(value);
}
complementaryFee(value) {
return value + 10;
}
}
class Tax1 extends Tax {
constructor() {
super();
}
overThousand(value) {
return value * 1.1;
}
}
class Tax2 extends Tax {
constructor() {
super();
}
overThousand(value) {
return value * 1.2;
}
}
Visitor
Visitor separates an algorithm from an object structure by moving the hierarchy of methods into one object.
Example:
function bonusVisitor(employee) {
if (employee instanceof Manager)
employee.bonus = employee.salary * 2;
if (employee instanceof Developer)
employee.bonus = employee.salary;
}
class Employee {
constructor(salary) {
this.bonus = 0;
this.salary = salary;
}
accept(visitor) {
visitor(this);
}
}
class Manager extends Employee {
constructor(salary) {
super(salary);
}
}
class Developer extends Employee {
constructor(salary) {
super(salary);
}
}
Last updated
Was this helpful?