Behavioral
Behavioral#
Behavioral design patterns are concerned with the assignment of responsibilities between objects and how they communicate. They focus on improving the flexibility and control of object interactions. These patterns help in defining communication patterns between objects, providing solutions for common problems related to object interaction and responsibility assignment.
The Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
Key Concepts#
- Handler: An interface or abstract class that defines a method for handling requests and an optional reference to the next handler in the chain.
- Concrete Handler: A class that implements the handler interface and decides whether to handle the request or pass it to the next handler.
- Client: Initiates the request and relies on the chain to process it.
Benefits#
- Reduces coupling between sender and receiver.
- Adds flexibility in assigning responsibilities to objects.
- Supports dynamic addition or removal of responsibilities.
The following diagram illustrates the Chain of Responsibility pattern with our guitar setup example, showing how requests pass through a chain of handlers until one handles it:
classDiagram
class SetupHandler {
<<interface>>
+setNextHandler(SetupHandler)
+handleSetup(String)
}
class StringSetupHandler {
-nextHandler: SetupHandler
+setNextHandler(SetupHandler)
+handleSetup(String)
}
class IntonationSetupHandler {
-nextHandler: SetupHandler
+setNextHandler(SetupHandler)
+handleSetup(String)
}
SetupHandler <|.. StringSetupHandler
SetupHandler <|.. IntonationSetupHandler
StringSetupHandler o-- SetupHandler : next
IntonationSetupHandler o-- SetupHandler : next
Each handler decides whether to process the request or pass it along the chain, creating a flexible and decoupled request-processing pipeline. Let's see this pattern in action.
Example#
Let's consider a guitar setup process where different technicians handle different aspects of the setup based on their expertise.
// The Handler interface
interface SetupHandler {
void setNextHandler(SetupHandler handler);
void handleSetup(String setupType);
}
// The Concrete handler for string setup
class StringSetupHandler implements SetupHandler {
private SetupHandler nextHandler;
@Override
public void setNextHandler(SetupHandler handler) {
this.nextHandler = handler;
}
@Override
public void handleSetup(String setupType) {
if (setupType.equals("Strings")) {
System.out.println("String setup handled by StringSetupHandler.");
} else if (nextHandler != null) {
nextHandler.handleSetup(setupType);
} else {
System.out.println("Cannot handle this type of setup.");
}
}
}
// The Concrete handler for intonation setup
class IntonationSetupHandler implements SetupHandler {
private SetupHandler nextHandler;
@Override
public void setNextHandler(SetupHandler handler) {
this.nextHandler = handler;
}
@Override
public void handleSetup(String setupType) {
if (setupType.equals("Intonation")) {
System.out.println("Intonation setup handled by IntonationSetupHandler.");
} else if (nextHandler != null) {
nextHandler.handleSetup(setupType);
} else {
System.out.println("Cannot handle this type of setup.");
}
}
}
// Client code
public class Main {
public static void main(String[] args) {
SetupHandler stringHandler = new StringSetupHandler();
SetupHandler intonationHandler = new IntonationSetupHandler();
stringHandler.setNextHandler(intonationHandler);
stringHandler.handleSetup("Strings"); // Output: String setup handled by StringSetupHandler.
stringHandler.handleSetup("Intonation"); // Output: Intonation setup handled by IntonationSetupHandler.
stringHandler.handleSetup("Action"); // Output: Cannot handle this type of setup.
}
}
// The Handler interface
interface SetupHandler {
fun setNextHandler(handler: SetupHandler)
fun handleSetup(setupType: String)
}
// The Concrete handler for string setup
class StringSetupHandler : SetupHandler {
private var nextHandler: SetupHandler? = null
override fun setNextHandler(handler: SetupHandler) {
this.nextHandler = handler
}
override fun handleSetup(setupType: String) {
if (setupType == "Strings") {
println("String setup handled by StringSetupHandler.")
} else if (nextHandler != null) {
nextHandler?.handleSetup(setupType)
} else {
println("Cannot handle this type of setup.")
}
}
}
// The Concrete handler for intonation setup
class IntonationSetupHandler : SetupHandler {
private var nextHandler: SetupHandler? = null
override fun setNextHandler(handler: SetupHandler) {
this.nextHandler = handler
}
override fun handleSetup(setupType: String) {
if (setupType == "Intonation") {
println("Intonation setup handled by IntonationSetupHandler.")
} else if (nextHandler != null) {
nextHandler?.handleSetup(setupType)
} else {
println("Cannot handle this type of setup.")
}
}
}
// Client code
fun main() {
val stringHandler = StringSetupHandler()
val intonationHandler = IntonationSetupHandler()
stringHandler.setNextHandler(intonationHandler)
stringHandler.handleSetup("Strings") // Output: String setup handled by StringSetupHandler.
stringHandler.handleSetup("Intonation") // Output: Intonation setup handled by IntonationSetupHandler.
stringHandler.handleSetup("Action") // Output: Cannot handle this type of setup.
}
// The Handler interface
interface SetupHandler {
setNextHandler(handler: SetupHandler): void;
handleSetup(setupType: string): void;
}
// The Concrete handler for string setup
class StringSetupHandler implements SetupHandler {
private nextHandler: SetupHandler | null = null;
setNextHandler(handler: SetupHandler): void {
this.nextHandler = handler;
}
handleSetup(setupType: string): void {
if (setupType === "Strings") {
console.log("String setup handled by StringSetupHandler.");
} else if (this.nextHandler !== null) {
this.nextHandler.handleSetup(setupType);
} else {
console.log("Cannot handle this type of setup.");
}
}
}
// The Concrete handler for intonation setup
class IntonationSetupHandler implements SetupHandler {
private nextHandler: SetupHandler | null = null;
setNextHandler(handler: SetupHandler): void {
this.nextHandler = handler;
}
handleSetup(setupType: string): void {
if (setupType === "Intonation") {
console.log("Intonation setup handled by IntonationSetupHandler.");
} else if (this.nextHandler !== null) {
this.nextHandler.handleSetup(setupType);
} else {
console.log("Cannot handle this type of setup.");
}
}
}
// Client code
const stringHandler = new StringSetupHandler();
const intonationHandler = new IntonationSetupHandler();
stringHandler.setNextHandler(intonationHandler);
stringHandler.handleSetup("Strings"); // Output: String setup handled by StringSetupHandler.
stringHandler.handleSetup("Intonation"); // Output: Intonation setup handled by IntonationSetupHandler.
stringHandler.handleSetup("Action"); // Output: Cannot handle this type of setup.
// The Handler interface
abstract class SetupHandler {
void setNextHandler(SetupHandler handler);
void handleSetup(String setupType);
}
// The Concrete handler for string setup
class StringSetupHandler implements SetupHandler {
SetupHandler? nextHandler;
@override
void setNextHandler(SetupHandler handler) {
this.nextHandler = handler;
}
@override
void handleSetup(String setupType) {
if (setupType == "Strings") {
print("String setup handled by StringSetupHandler.");
} else if (nextHandler != null) {
nextHandler!.handleSetup(setupType);
} else {
print("Cannot handle this type of setup.");
}
}
}
// The Concrete handler for intonation setup
class IntonationSetupHandler implements SetupHandler {
SetupHandler? nextHandler;
@override
void setNextHandler(SetupHandler handler) {
this.nextHandler = handler;
}
@override
void handleSetup(String setupType) {
if (setupType == "Intonation") {
print("Intonation setup handled by IntonationSetupHandler.");
} else if (nextHandler != null) {
nextHandler!.handleSetup(setupType);
} else {
print("Cannot handle this type of setup.");
}
}
}
// Client code
void main() {
SetupHandler stringHandler = StringSetupHandler();
SetupHandler intonationHandler = IntonationSetupHandler();
stringHandler.setNextHandler(intonationHandler);
stringHandler.handleSetup("Strings"); // Output: String setup handled by StringSetupHandler.
stringHandler.handleSetup("Intonation"); // Output: Intonation setup handled by IntonationSetupHandler.
stringHandler.handleSetup("Action"); // Output: Cannot handle this type of setup.
}
// The Handler protocol
protocol SetupHandler {
func setNextHandler(handler: SetupHandler?)
func handleSetup(setupType: String)
}
// The Concrete handler for string setup
class StringSetupHandler: SetupHandler {
private var nextHandler: SetupHandler?
func setNextHandler(handler: SetupHandler?) {
self.nextHandler = handler
}
func handleSetup(setupType: String) {
if setupType == "Strings" {
print("String setup handled by StringSetupHandler.")
} else if nextHandler != nil {
nextHandler?.handleSetup(setupType: setupType)
} else {
print("Cannot handle this type of setup.")
}
}
}
// The Concrete handler for intonation setup
class IntonationSetupHandler: SetupHandler {
private var nextHandler: SetupHandler?
func setNextHandler(handler: SetupHandler?) {
self.nextHandler = handler
}
func handleSetup(setupType: String) {
if setupType == "Intonation" {
print("Intonation setup handled by IntonationSetupHandler.")
} else if nextHandler != nil {
nextHandler?.handleSetup(setupType: setupType)
} else {
print("Cannot handle this type of setup.")
}
}
}
// Client code
let stringHandler = StringSetupHandler()
let intonationHandler = IntonationSetupHandler()
stringHandler.setNextHandler(handler: intonationHandler)
stringHandler.handleSetup(setupType: "Strings") // Output: String setup handled by StringSetupHandler.
stringHandler.handleSetup(setupType: "Intonation") // Output: Intonation setup handled by IntonationSetupHandler.
stringHandler.handleSetup(setupType: "Action") // Output: Cannot handle this type of setup.
# The Handler interface
class SetupHandler:
def set_next_handler(self, handler):
pass
def handle_setup(self, setup_type):
pass
# The Concrete handler for string setup
class StringSetupHandler(SetupHandler):
def __init__(self):
self.next_handler = None
def set_next_handler(self, handler):
self.next_handler = handler
def handle_setup(self, setup_type):
if setup_type == "Strings":
print("String setup handled by StringSetupHandler.")
elif self.next_handler is not None:
self.next_handler.handle_setup(setup_type)
else:
print("Cannot handle this type of setup.")
# The Concrete handler for intonation setup
class IntonationSetupHandler(SetupHandler):
def __init__(self):
self.next_handler = None
def set_next_handler(self, handler):
self.next_handler = handler
def handle_setup(self, setup_type):
if setup_type == "Intonation":
print("Intonation setup handled by IntonationSetupHandler.")
elif self.next_handler is not None:
self.next_handler.handle_setup(setup_type)
else:
print("Cannot handle this type of setup.")
# Client code
string_handler = StringSetupHandler()
intonation_handler = IntonationSetupHandler()
string_handler.set_next_handler(intonation_handler)
string_handler.handle_setup("Strings") # Output: String setup handled by StringSetupHandler.
string_handler.handle_setup("Intonation") # Output: Intonation setup handled by IntonationSetupHandler.
string_handler.handle_setup("Action") # Output: Cannot handle this type of setup.
Summary#
The Chain of Responsibility pattern provides a flexible way to handle requests by passing them through a chain of handlers. Each handler decides whether to process the request or pass it to the next handler in the chain, reducing coupling between the sender and receiver and allowing for dynamic addition or removal of responsibilities.
The Command pattern is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.
Key Concepts#
- Command: Declares an interface for all commands.
- Concrete Command: Defines a binding between a receiver object and an action. Implements the command by invoking the corresponding operation(s) on receiver.
- Invoker: Asks the command to carry out the request.
- Receiver: Knows how to perform the operations associated with carrying out a request.
- Client: Creates concrete command objects and sets its receiver.
Benefits#
- Decouples objects that invoke an operation from those that perform it.
- Supports undoable operations.
- Supports queuing of commands.
The following diagram illustrates the Command pattern with our guitar effects example, showing how commands encapsulate actions and enable undo functionality:
classDiagram
class Command {
<<interface>>
+execute()
+undo()
}
class ApplyEffectCommand {
-guitarEffect: GuitarEffect
+execute()
+undo()
}
class GuitarEffect {
-effectName: String
+applyEffect()
+removeEffect()
}
class GuitarEffectInvoker {
-command: Command
+setCommand(Command)
+executeCommand()
+undoCommand()
}
Command <|.. ApplyEffectCommand
ApplyEffectCommand o-- GuitarEffect
GuitarEffectInvoker o-- Command
The Command encapsulates a request as an object, allowing you to parameterize clients with queues, requests, and operations while supporting undo. Let's see this pattern in action.
Example#
Let's consider a scenario where we want to implement different guitar effects as commands that can be executed and potentially undone.
// The Command interface
interface Command {
void execute();
void undo();
}
// The Receiver
class GuitarEffect {
private String effectName;
public GuitarEffect(String effectName) {
this.effectName = effectName;
}
public void applyEffect() {
System.out.println("Applying " + effectName + " effect.");
}
public void removeEffect() {
System.out.println("Removing " + effectName + " effect.");
}
}
// The Concrete Command
class ApplyEffectCommand implements Command {
private GuitarEffect guitarEffect;
public ApplyEffectCommand(GuitarEffect guitarEffect) {
this.guitarEffect = guitarEffect;
}
@Override
public void execute() {
guitarEffect.applyEffect();
}
@Override
public void undo() {
guitarEffect.removeEffect();
}
}
// The Invoker
class GuitarEffectInvoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
public void undoCommand() {
command.undo();
}
}
// Client
public class Main {
public static void main(String[] args) {
GuitarEffect chorus = new GuitarEffect("Chorus");
ApplyEffectCommand applyChorusCommand = new ApplyEffectCommand(chorus);
GuitarEffectInvoker invoker = new GuitarEffectInvoker();
invoker.setCommand(applyChorusCommand);
invoker.executeCommand(); // Output: Applying Chorus effect.
invoker.undoCommand(); // Output: Removing Chorus effect.
}
}
// The Command interface
interface Command {
fun execute()
fun undo()
}
// The Receiver
class GuitarEffect(private val effectName: String) {
fun applyEffect() {
println("Applying $effectName effect.")
}
fun removeEffect() {
println("Removing $effectName effect.")
}
}
// The Concrete Command
class ApplyEffectCommand(private val guitarEffect: GuitarEffect) : Command {
override fun execute() {
guitarEffect.applyEffect()
}
override fun undo() {
guitarEffect.removeEffect()
}
}
// The Invoker
class GuitarEffectInvoker {
private lateinit var command: Command
fun setCommand(command: Command) {
this.command = command
}
fun executeCommand() {
command.execute()
}
fun undoCommand() {
command.undo()
}
}
// Client
fun main() {
val chorus = GuitarEffect("Chorus")
val applyChorusCommand = ApplyEffectCommand(chorus)
val invoker = GuitarEffectInvoker()
invoker.setCommand(applyChorusCommand)
invoker.executeCommand() // Output: Applying Chorus effect.
invoker.undoCommand() // Output: Removing Chorus effect.
}
// The Command interface
interface Command {
execute(): void;
undo(): void;
}
// The Receiver
class GuitarEffect {
private effectName: string;
constructor(effectName: string) {
this.effectName = effectName;
}
public applyEffect(): void {
console.log(`Applying ${this.effectName} effect.`);
}
public removeEffect(): void {
console.log(`Removing ${this.effectName} effect.`);
}
}
// The Concrete Command
class ApplyEffectCommand implements Command {
private guitarEffect: GuitarEffect;
constructor(guitarEffect: GuitarEffect) {
this.guitarEffect = guitarEffect;
}
public execute(): void {
this.guitarEffect.applyEffect();
}
public undo(): void {
this.guitarEffect.removeEffect();
}
}
// The Invoker
class GuitarEffectInvoker {
private command: Command;
public setCommand(command: Command): void {
this.command = command;
}
public executeCommand(): void {
this.command.execute();
}
public undoCommand(): void {
this.command.undo();
}
}
// Client
const chorus = new GuitarEffect("Chorus");
const applyChorusCommand = new ApplyEffectCommand(chorus);
const invoker = new GuitarEffectInvoker();
invoker.setCommand(applyChorusCommand);
invoker.executeCommand(); // Output: Applying Chorus effect.
invoker.undoCommand(); // Output: Removing Chorus effect.
// The Command interface
abstract class Command {
void execute();
void undo();
}
// The Receiver
class GuitarEffect {
final String effectName;
GuitarEffect(this.effectName);
void applyEffect() {
print('Applying $effectName effect.');
}
void removeEffect() {
print('Removing $effectName effect.');
}
}
// The Concrete Command
class ApplyEffectCommand implements Command {
final GuitarEffect guitarEffect;
ApplyEffectCommand(this.guitarEffect);
@override
void execute() {
guitarEffect.applyEffect();
}
@override
void undo() {
guitarEffect.removeEffect();
}
}
// The Invoker
class GuitarEffectInvoker {
late Command _command;
void setCommand(Command command) {
_command = command;
}
void executeCommand() {
_command.execute();
}
void undoCommand() {
_command.undo();
}
}
// Client
void main() {
final chorus = GuitarEffect('Chorus');
final applyChorusCommand = ApplyEffectCommand(chorus);
final invoker = GuitarEffectInvoker();
invoker.setCommand(applyChorusCommand);
invoker.executeCommand(); // Output: Applying Chorus effect.
invoker.undoCommand(); // Output: Removing Chorus effect.
}
// The Command interface
protocol Command {
func execute()
func undo()
}
// The Receiver
class GuitarEffect {
private let effectName: String
init(effectName: String) {
self.effectName = effectName
}
func applyEffect() {
print("Applying \(effectName) effect.")
}
func removeEffect() {
print("Removing \(effectName) effect.")
}
}
// The Concrete Command
class ApplyEffectCommand: Command {
private let guitarEffect: GuitarEffect
init(guitarEffect: GuitarEffect) {
self.guitarEffect = guitarEffect
}
func execute() {
guitarEffect.applyEffect()
}
func undo() {
guitarEffect.removeEffect()
}
}
// The Invoker
class GuitarEffectInvoker {
private var command: Command?
func setCommand(command: Command) {
self.command = command
}
func executeCommand() {
command?.execute()
}
func undoCommand() {
command?.undo()
}
}
// Client
let chorus = GuitarEffect(effectName: "Chorus")
let applyChorusCommand = ApplyEffectCommand(guitarEffect: chorus)
let invoker = GuitarEffectInvoker()
invoker.setCommand(command: applyChorusCommand)
invoker.executeCommand() // Output: Applying Chorus effect.
invoker.undoCommand() // Output: Removing Chorus effect.
# The Command interface
class Command:
def execute(self):
pass
def undo(self):
pass
# The Receiver
class GuitarEffect:
def __init__(self, effect_name):
self.effect_name = effect_name
def apply_effect(self):
print(f"Applying {self.effect_name} effect.")
def remove_effect(self):
print(f"Removing {self.effect_name} effect.")
# The Concrete Command
class ApplyEffectCommand(Command):
def __init__(self, guitar_effect):
self.guitar_effect = guitar_effect
def execute(self):
self.guitar_effect.apply_effect()
def undo(self):
self.guitar_effect.remove_effect()
# The Invoker
class GuitarEffectInvoker:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def execute_command(self):
self.command.execute()
def undo_command(self):
self.command.undo()
# Client
chorus = GuitarEffect("Chorus")
apply_chorus_command = ApplyEffectCommand(chorus)
invoker = GuitarEffectInvoker()
invoker.set_command(apply_chorus_command)
invoker.execute_command() # Output: Applying Chorus effect.
invoker.undo_command() # Output: Removing Chorus effect.
Summary#
The Command pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. It promotes loose coupling between objects by separating the request's sender from its receiver.
The Iterator pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It abstracts the traversal logic, allowing clients to iterate over different data structures in a uniform way.
Key Concepts#
- Iterator: Defines an interface for accessing and traversing elements.
- Concrete Iterator: Implements the Iterator interface and keeps track of the current position while traversing the aggregate.
- Aggregate: Defines an interface for creating an Iterator object.
- Concrete Aggregate: Implements the Aggregate interface and returns an instance of a Concrete Iterator.
- Client: Uses the iterator to traverse the aggregate.
Benefits#
- Simplifies the aggregate interface by removing traversal logic.
- Supports different ways to traverse an aggregate.
- Allows multiple traversals to be pending on the same aggregate.
The following diagram illustrates the Iterator pattern with our guitar pedals example, showing how the iterator provides uniform access to elements without exposing the collection's structure:
classDiagram
class Iterator~T~ {
<<interface>>
+hasNext() boolean
+next() T
}
class PedalIterator {
-pedals: String[]
-position: int
+hasNext() boolean
+next() String
}
class PedalCollection {
<<interface>>
+createIterator() Iterator~String~
}
class GuitarPedalCollection {
-pedals: String[]
+createIterator() Iterator~String~
}
Iterator <|.. PedalIterator
PedalCollection <|.. GuitarPedalCollection
PedalCollection ..> Iterator : creates
GuitarPedalCollection ..> PedalIterator : creates
The Iterator provides a standardized way to traverse collections without exposing their internal structure, enabling uniform access patterns. Let's see this in action.
Example#
Let's consider a scenario where we want to iterate over a collection of guitar pedals.
// The Iterator interface
interface Iterator<T> {
boolean hasNext();
T next();
}
// The Aggregate interface
interface PedalCollection {
Iterator<String> createIterator();
}
// The Concrete Iterator
class PedalIterator implements Iterator<String> {
private String[] pedals;
private int position;
public PedalIterator(String[] pedals) {
this.pedals = pedals;
this.position = 0;
}
@Override
public boolean hasNext() {
return position < pedals.length;
}
@Override
public String next() {
if (hasNext()) {
return pedals[position++];
}
return null;
}
}
// The Concrete Aggregate
class GuitarPedalCollection implements PedalCollection {
private String[] pedals;
public GuitarPedalCollection(String[] pedals) {
this.pedals = pedals;
}
@Override
public Iterator<String> createIterator() {
return new PedalIterator(pedals);
}
}
// Client
public class Main {
public static void main(String[] args) {
String[] pedals = {"Overdrive", "Delay", "Chorus"};
GuitarPedalCollection pedalCollection = new GuitarPedalCollection(pedals);
Iterator<String> iterator = pedalCollection.createIterator();
while (iterator.hasNext()) {
System.out.println("Pedal: " + iterator.next());
}
}
}
// The Iterator interface
interface Iterator<T> {
fun hasNext(): Boolean
fun next(): T?
}
// The Aggregate interface
interface PedalCollection {
fun createIterator(): Iterator<String>
}
// The Concrete Iterator
class PedalIterator(private val pedals: Array<String>) : Iterator<String> {
private var position: Int = 0
override fun hasNext(): Boolean {
return position < pedals.size
}
override fun next(): String? {
if (hasNext()) {
return pedals[position++]
}
return null
}
}
// The Concrete Aggregate
class GuitarPedalCollection(private val pedals: Array<String>) : PedalCollection {
override fun createIterator(): Iterator<String> {
return PedalIterator(pedals)
}
}
// Client
fun main() {
val pedals = arrayOf("Overdrive", "Delay", "Chorus")
val pedalCollection = GuitarPedalCollection(pedals)
val iterator = pedalCollection.createIterator()
while (iterator.hasNext()) {
println("Pedal: ${iterator.next()}")
}
}
// The Iterator interface
interface Iterator<T> {
hasNext(): boolean;
next(): T | null;
}
// The Aggregate interface
interface PedalCollection {
createIterator(): Iterator<string>;
}
// The Concrete Iterator
class PedalIterator implements Iterator<string> {
private pedals: string[];
private position: number = 0;
constructor(pedals: string[]) {
this.pedals = pedals;
}
hasNext(): boolean {
return this.position < this.pedals.length;
}
next(): string | null {
if (this.hasNext()) {
return this.pedals[this.position++];
}
return null;
}
}
// The Concrete Aggregate
class GuitarPedalCollection implements PedalCollection {
private pedals: string[];
constructor(pedals: string[]) {
this.pedals = pedals;
}
createIterator(): Iterator<string> {
return new PedalIterator(this.pedals);
}
}
// Client
function main() {
const pedals: string[] = ["Overdrive", "Delay", "Chorus"];
const pedalCollection: GuitarPedalCollection = new GuitarPedalCollection(pedals);
const iterator: Iterator<string> = pedalCollection.createIterator();
while (iterator.hasNext()) {
console.log("Pedal: " + iterator.next());
}
}
// The Iterator interface
abstract class Iterator<T> {
bool hasNext();
T? next();
}
// The Aggregate interface
abstract class PedalCollection {
Iterator<String> createIterator();
}
// The Concrete Iterator
class PedalIterator implements Iterator<String> {
final List<String> pedals;
int position = 0;
PedalIterator(this.pedals);
@override
bool hasNext() {
return position < pedals.length;
}
@override
String? next() {
if (hasNext()) {
return pedals[position++];
}
return null;
}
}
// The Concrete Aggregate
class GuitarPedalCollection implements PedalCollection {
final List<String> pedals;
GuitarPedalCollection(this.pedals);
@override
Iterator<String> createIterator() {
return PedalIterator(pedals);
}
}
// Client
void main() {
List<String> pedals = ["Overdrive", "Delay", "Chorus"];
GuitarPedalCollection pedalCollection = GuitarPedalCollection(pedals);
Iterator<String> iterator = pedalCollection.createIterator();
while (iterator.hasNext()) {
print("Pedal: ${iterator.next()}");
}
}
// The Iterator protocol
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
// The Aggregate protocol
protocol PedalCollection {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}
// The Concrete Iterator
struct PedalIterator: IteratorProtocol {
private let pedals: [String]
private var current = 0
init(pedals: [String]) {
self.pedals = pedals
}
mutating func next() -> String? {
guard current < pedals.count else {
return nil
}
let pedal = pedals[current]
current += 1
return pedal
}
}
// The Concrete Aggregate
struct GuitarPedalCollection: PedalCollection {
private let pedals: [String]
init(pedals: [String]) {
self.pedals = pedals
}
func makeIterator() -> PedalIterator {
return PedalIterator(pedals: pedals)
}
}
// Client
let pedals = ["Overdrive", "Delay", "Chorus"]
let pedalCollection = GuitarPedalCollection(pedals: pedals)
var iterator = pedalCollection.makeIterator()
while let pedal = iterator.next() {
print("Pedal: \(pedal)")
}
# The Iterator interface
class Iterator:
def has_next(self):
pass
def next(self):
pass
# The Aggregate interface
class PedalCollection:
def create_iterator(self):
pass
# The Concrete Iterator
class PedalIterator(Iterator):
def __init__(self, pedals):
self.pedals = pedals
self.position = 0
def has_next(self):
return self.position < len(self.pedals)
def next(self):
if self.has_next():
pedal = self.pedals[self.position]
self.position += 1
return pedal
return None
# The Concrete Aggregate
class GuitarPedalCollection(PedalCollection):
def __init__(self, pedals):
self.pedals = pedals
def create_iterator(self):
return PedalIterator(self.pedals)
# Client
pedals = ["Overdrive", "Delay", "Chorus"]
pedal_collection = GuitarPedalCollection(pedals)
iterator = pedal_collection.create_iterator()
while iterator.has_next():
print("Pedal:", iterator.next())
Summary#
The Iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It simplifies the aggregate interface, supports different ways to traverse an aggregate, and allows multiple traversals to be pending on the same aggregate.
The Mediator pattern is a behavioral design pattern that reduces coupling between objects by providing a central mediator object that handles all communication between them. Instead of objects communicating directly with each other, they communicate through the mediator, which knows how to route the communication appropriately.
Key Concepts#
- Mediator: Defines an interface for communicating with Colleague objects.
- Concrete Mediator: Implements the Mediator interface and coordinates communication between Colleague objects.
- Colleague: Defines an interface for communicating with other colleagues through the Mediator.
- Concrete Colleague: Implements the Colleague interface and communicates with other colleagues through the Mediator.
Benefits#
- Reduces coupling between objects.
- Centralizes control over communication.
- Simplifies object interactions.
The following diagram illustrates the Mediator pattern with our guitar effects example, showing how effects communicate through the mediator rather than directly with each other:
classDiagram
class Mediator {
<<interface>>
+sendMessage(String, Colleague)
}
class Colleague {
<<abstract>>
#mediator: Mediator
+receiveMessage(String)*
}
class GuitarEffectMediator {
-chorus: ChorusEffect
-delay: DelayEffect
-reverb: ReverbEffect
+setChorus(ChorusEffect)
+setDelay(DelayEffect)
+setReverb(ReverbEffect)
+sendMessage(String, Colleague)
}
class ChorusEffect {
+applyChorus(String)
+receiveMessage(String)
}
class DelayEffect {
+applyDelay(String)
+receiveMessage(String)
}
class ReverbEffect {
+applyReverb(String)
+receiveMessage(String)
}
Mediator <|.. GuitarEffectMediator
Colleague <|-- ChorusEffect
Colleague <|-- DelayEffect
Colleague <|-- ReverbEffect
Colleague --> Mediator : uses
GuitarEffectMediator --> ChorusEffect : knows
GuitarEffectMediator --> DelayEffect : knows
GuitarEffectMediator --> ReverbEffect : knows
The Mediator centralizes communication logic, so colleagues send messages through the mediator which then routes them to other colleagues, promoting loose coupling. Let's see this coordination in action.
Example#
Let's consider a scenario where we have different guitar effects (e.g., chorus, delay, reverb) that need to be coordinated by a central mediator.
// The Mediator interface
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
// The Colleague interface
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void receiveMessage(String message);
}
// The Concrete Mediator
class GuitarEffectMediator implements Mediator {
private ChorusEffect chorus;
private DelayEffect delay;
private ReverbEffect reverb;
public void setChorus(ChorusEffect chorus) {
this.chorus = chorus;
}
public void setDelay(DelayEffect delay) {
this.delay = delay;
}
public void setReverb(ReverbEffect reverb) {
this.reverb = reverb;
}
@Override
public void sendMessage(String message, Colleague colleague) {
if (colleague == chorus) {
delay.receiveMessage(message);
reverb.receiveMessage(message);
} else if (colleague == delay) {
chorus.receiveMessage(message);
reverb.receiveMessage(message);
} else if (colleague == reverb) {
chorus.receiveMessage(message);
delay.receiveMessage(message);
}
}
}
// The Concrete Colleague
class ChorusEffect extends Colleague {
public ChorusEffect(Mediator mediator) {
super(mediator);
}
public void applyChorus(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println("Chorus Effect received message: " + message);
}
}
// The Concrete Colleague
class DelayEffect extends Colleague {
public DelayEffect(Mediator mediator) {
super(mediator);
}
public void applyDelay(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println("Delay Effect received message: " + message);
}
}
// The Concrete Colleague
class ReverbEffect extends Colleague {
public ReverbEffect(Mediator mediator) {
super(mediator);
}
public void applyReverb(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println("Reverb Effect received message: " + message);
}
}
// Client
public class Main {
public static void main(String[] args) {
GuitarEffectMediator mediator = new GuitarEffectMediator();
ChorusEffect chorus = new ChorusEffect(mediator);
DelayEffect delay = new DelayEffect(mediator);
ReverbEffect reverb = new ReverbEffect(mediator);
mediator.setChorus(chorus);
mediator.setDelay(delay);
mediator.setReverb(reverb);
chorus.applyChorus("Chorus applied");
delay.applyDelay("Delay applied");
reverb.applyReverb("Reverb applied");
}
}
// The Mediator interface
interface Mediator {
fun sendMessage(message: String, colleague: Colleague)
}
// The Colleague interface
abstract class Colleague(protected val mediator: Mediator) {
abstract fun receiveMessage(message: String)
}
// The Concrete Mediator
class GuitarEffectMediator : Mediator {
var chorus: ChorusEffect? = null
var delay: DelayEffect? = null
var reverb: ReverbEffect? = null
override fun sendMessage(message: String, colleague: Colleague) {
when (colleague) {
chorus -> {
delay?.receiveMessage(message)
reverb?.receiveMessage(message)
}
delay -> {
chorus?.receiveMessage(message)
reverb?.receiveMessage(message)
}
reverb -> {
chorus?.receiveMessage(message)
delay?.receiveMessage(message)
}
}
}
}
// The Concrete Colleague
class ChorusEffect(mediator: Mediator) : Colleague(mediator) {
fun applyChorus(message: String) {
mediator.sendMessage(message, this)
}
override fun receiveMessage(message: String) {
println("Chorus Effect received message: $message")
}
}
// The Concrete Colleague
class DelayEffect(mediator: Mediator) : Colleague(mediator) {
fun applyDelay(message: String) {
mediator.sendMessage(message, this)
}
override fun receiveMessage(message: String) {
println("Delay Effect received message: $message")
}
}
// The Concrete Colleague
class ReverbEffect(mediator: Mediator) : Colleague(mediator) {
fun applyReverb(message: String) {
mediator.sendMessage(message, this)
}
override fun receiveMessage(message: String) {
println("Reverb Effect received message: $message")
}
}
// Client
fun main() {
val mediator = GuitarEffectMediator()
val chorus = ChorusEffect(mediator)
val delay = DelayEffect(mediator)
val reverb = ReverbEffect(mediator)
mediator.chorus = chorus
mediator.delay = delay
mediator.reverb = reverb
chorus.applyChorus("Chorus applied")
delay.applyDelay("Delay applied")
reverb.applyReverb("Reverb applied")
}
// The Mediator interface
interface Mediator {
sendMessage(message: string, colleague: Colleague): void;
}
// The Colleague interface
abstract class Colleague {
protected mediator: Mediator;
constructor(mediator: Mediator) {
this.mediator = mediator;
}
abstract receiveMessage(message: string): void;
}
// The Concrete Mediator
class GuitarEffectMediator implements Mediator {
private chorus: ChorusEffect | null = null;
private delay: DelayEffect | null = null;
private reverb: ReverbEffect | null = null;
setChorus(chorus: ChorusEffect) {
this.chorus = chorus;
}
setDelay(delay: DelayEffect) {
this.delay = delay;
}
setReverb(reverb: ReverbEffect) {
this.reverb = reverb;
}
sendMessage(message: string, colleague: Colleague): void {
if (colleague === this.chorus) {
this.delay?.receiveMessage(message);
this.reverb?.receiveMessage(message);
} else if (colleague === this.delay) {
this.chorus?.receiveMessage(message);
this.reverb?.receiveMessage(message);
} else if (colleague === this.reverb) {
this.chorus?.receiveMessage(message);
this.delay?.receiveMessage(message);
}
}
}
// The Concrete Colleague
class ChorusEffect extends Colleague {
constructor(mediator: Mediator) {
super(mediator);
}
applyChorus(message: string) {
this.mediator.sendMessage(message, this);
}
receiveMessage(message: string): void {
console.log("Chorus Effect received message: " + message);
}
}
// The Concrete Colleague
class DelayEffect extends Colleague {
constructor(mediator: Mediator) {
super(mediator);
}
applyDelay(message: string) {
this.mediator.sendMessage(message, this);
}
receiveMessage(message: string): void {
console.log("Delay Effect received message: " + message);
}
}
// The Concrete Colleague
class ReverbEffect extends Colleague {
constructor(mediator: Mediator) {
super(mediator);
}
applyReverb(message: string) {
this.mediator.sendMessage(message, this);
}
receiveMessage(message: string): void {
console.log("Reverb Effect received message: " + message);
}
}
// Client
function main() {
const mediator = new GuitarEffectMediator();
const chorus = new ChorusEffect(mediator);
const delay = new DelayEffect(mediator);
const reverb = new ReverbEffect(mediator);
mediator.setChorus(chorus);
mediator.setDelay(delay);
mediator.setReverb(reverb);
chorus.applyChorus("Chorus applied");
delay.applyDelay("Delay applied");
reverb.applyReverb("Reverb applied");
}
// The Mediator interface
abstract class Mediator {
void sendMessage(String message, Colleague colleague);
}
// The Colleague interface
abstract class Colleague {
final Mediator mediator;
Colleague(this.mediator);
void receiveMessage(String message);
}
// The Concrete Mediator
class GuitarEffectMediator implements Mediator {
ChorusEffect? chorus;
DelayEffect? delay;
ReverbEffect? reverb;
void setChorus(ChorusEffect chorus) {
this.chorus = chorus;
}
void setDelay(DelayEffect delay) {
this.delay = delay;
}
void setReverb(ReverbEffect reverb) {
this.reverb = reverb;
}
@override
void sendMessage(String message, Colleague colleague) {
if (colleague == chorus) {
delay?.receiveMessage(message);
reverb?.receiveMessage(message);
} else if (colleague == delay) {
chorus?.receiveMessage(message);
reverb?.receiveMessage(message);
} else if (colleague == reverb) {
chorus?.receiveMessage(message);
delay?.receiveMessage(message);
}
}
}
// The Concrete Colleague
class ChorusEffect extends Colleague {
ChorusEffect(Mediator mediator) : super(mediator);
void applyChorus(String message) {
mediator.sendMessage(message, this);
}
@override
void receiveMessage(String message) {
print("Chorus Effect received message: $message");
}
}
// The Concrete Colleague
class DelayEffect extends Colleague {
DelayEffect(Mediator mediator) : super(mediator);
void applyDelay(String message) {
mediator.sendMessage(message, this);
}
@override
void receiveMessage(String message) {
print("Delay Effect received message: $message");
}
}
// The Concrete Colleague
class ReverbEffect extends Colleague {
ReverbEffect(Mediator mediator) : super(mediator);
void applyReverb(String message) {
mediator.sendMessage(message, this);
}
@override
void receiveMessage(String message) {
print("Reverb Effect received message: $message");
}
}
// Client
void main() {
GuitarEffectMediator mediator = GuitarEffectMediator();
ChorusEffect chorus = ChorusEffect(mediator);
DelayEffect delay = DelayEffect(mediator);
ReverbEffect reverb = ReverbEffect(mediator);
mediator.setChorus(chorus);
mediator.setDelay(delay);
mediator.setReverb(reverb);
chorus.applyChorus("Chorus applied");
delay.applyDelay("Delay applied");
reverb.applyReverb("Reverb applied");
}
// The Mediator protocol
protocol Mediator {
func sendMessage(message: String, colleague: Colleague)
}
// The Colleague class
class Colleague {
weak var mediator: Mediator?
init(mediator: Mediator?) {
self.mediator = mediator
}
func receiveMessage(message: String) {
fatalError("receiveMessage() has not been implemented")
}
}
// The Concrete Mediator
class GuitarEffectMediator: Mediator {
var chorus: ChorusEffect?
var delay: DelayEffect?
var reverb: ReverbEffect?
func sendMessage(message: String, colleague: Colleague) {
if colleague is ChorusEffect {
delay?.receiveMessage(message: message)
reverb?.receiveMessage(message: message)
} else if colleague is DelayEffect {
chorus?.receiveMessage(message: message)
reverb?.receiveMessage(message: message)
} else if colleague is ReverbEffect {
chorus?.receiveMessage(message: message)
delay?.receiveMessage(message: message)
}
}
}
// The Concrete Colleague
class ChorusEffect: Colleague {
func applyChorus(message: String) {
mediator?.sendMessage(message: message, colleague: self)
}
override func receiveMessage(message: String) {
print("Chorus Effect received message: \(message)")
}
}
// The Concrete Colleague
class DelayEffect: Colleague {
func applyDelay(message: String) {
mediator?.sendMessage(message: message, colleague: self)
}
override func receiveMessage(message: String) {
print("Delay Effect received message: \(message)")
}
}
// The Concrete Colleague
class ReverbEffect: Colleague {
func applyReverb(message: String) {
mediator?.sendMessage(message: message, colleague: self)
}
override func receiveMessage(message: String) {
print("Reverb Effect received message: \(message)")
}
}
// Client
let mediator = GuitarEffectMediator()
let chorus = ChorusEffect(mediator: mediator)
let delay = DelayEffect(mediator: mediator)
let reverb = ReverbEffect(mediator: mediator)
mediator.chorus = chorus
mediator.delay = delay
mediator.reverb = reverb
chorus.applyChorus(message: "Chorus applied")
delay.applyDelay(message: "Delay applied")
reverb.applyReverb(message: "Reverb applied")
# The Mediator interface
class Mediator:
def send_message(self, message, colleague):
pass
# The Colleague interface
class Colleague:
def __init__(self, mediator):
self.mediator = mediator
def receive_message(self, message):
pass
# Concrete Mediator
class GuitarEffectMediator(Mediator):
def __init__(self):
self.chorus = None
self.delay = None
self.reverb = None
def send_message(self, message, colleague):
if colleague == self.chorus:
if self.delay:
self.delay.receive_message(message)
if self.reverb:
self.reverb.receive_message(message)
elif colleague == self.delay:
if self.chorus:
self.chorus.receive_message(message)
if self.reverb:
self.reverb.receive_message(message)
elif colleague == self.reverb:
if self.chorus:
self.chorus.receive_message(message)
if self.delay:
self.delay.receive_message(message)
# Concrete Colleague
class ChorusEffect(Colleague):
def __init__(self, mediator):
super().__init__(mediator)
def apply_chorus(self, message):
self.mediator.send_message(message, self)
def receive_message(self, message):
print(f"Chorus Effect received message: {message}")
# Concrete Colleague
class DelayEffect(Colleague):
def __init__(self, mediator):
super().__init__(mediator)
def apply_delay(self, message):
self.mediator.send_message(message, self)
def receive_message(self, message):
print(f"Delay Effect received message: {message}")
# Concrete Colleague
class ReverbEffect(Colleague):
def __init__(self, mediator):
super().__init__(mediator)
def apply_reverb(self, message):
self.mediator.send_message(message, self)
def receive_message(self, message):
print(f"Reverb Effect received message: {message}")
# Client
mediator = GuitarEffectMediator()
chorus = ChorusEffect(mediator)
delay = DelayEffect(mediator)
reverb = ReverbEffect(mediator)
mediator.chorus = chorus
mediator.delay = delay
mediator.reverb = reverb
chorus.apply_chorus("Chorus applied")
delay.apply_delay("Delay applied")
reverb.apply_reverb("Reverb applied")
Summary#
The Mediator pattern reduces coupling between objects by providing a central mediator object that handles all communication between them. It centralizes control over communication and simplifies object interactions.
The Memento pattern is a behavioral design pattern that provides the ability to restore an object to its previous state. It does this without violating encapsulation.
Key Concepts#
- Memento: Stores the internal state of the Originator object. The memento may store all or part of the Originator's internal state.
- Originator: Creates a memento containing a snapshot of its current internal state. Uses the memento to restore its internal state.
- Caretaker: Holds the mementos but does not examine their contents. It is responsible for the memento's safekeeping.
Benefits#
- Provides a way to restore an object to its previous state.
- Preserves encapsulation by not exposing the object's internal state.
- Simplifies the Originator by delegating state saving and restoring to the Memento and Caretaker.
The following diagram illustrates the Memento pattern with our guitar tuning example, showing how Guitar saves and restores its state through TuningMemento without exposing internals:
classDiagram
class TuningMemento {
-tuning: String
+getTuning() String
}
class Guitar {
-tuning: String
+setTuning(String)
+saveTuningToMemento() TuningMemento
+restoreTuningFromMemento(TuningMemento)
}
class TuningCareTaker {
-memento: TuningMemento
+setMemento(TuningMemento)
+getMemento() TuningMemento
}
Guitar ..> TuningMemento : creates
TuningCareTaker o-- TuningMemento
The Memento enables capturing and externalizing an object's state so it can be restored later, all without violating encapsulation. Let's see this undo mechanism in action.
Example#
Let's consider a scenario where we want to save and restore the state of a guitar's tuning settings.
// The Memento
class TuningMemento {
private final String tuning;
public TuningMemento(String tuning) {
this.tuning = tuning;
}
public String getTuning() {
return tuning;
}
}
// The Originator
class Guitar {
private String tuning;
public void setTuning(String tuning) {
this.tuning = tuning;
System.out.println("Tuning set to: " + tuning);
}
public TuningMemento saveTuningToMemento() {
System.out.println("Saving tuning to Memento.");
return new TuningMemento(tuning);
}
public void restoreTuningFromMemento(TuningMemento memento) {
tuning = memento.getTuning();
System.out.println("Restoring tuning from Memento: " + tuning);
}
}
// The Caretaker
class TuningCareTaker {
private TuningMemento memento;
public void setMemento(TuningMemento memento) {
this.memento = memento;
}
public TuningMemento getMemento() {
return memento;
}
}
// Client
public class Main {
public static void main(String[] args) {
Guitar guitar = new Guitar();
TuningCareTaker careTaker = new TuningCareTaker();
guitar.setTuning("E Standard");
careTaker.setMemento(guitar.saveTuningToMemento());
guitar.setTuning("Drop D");
System.out.println("Current tuning is: " + guitar.tuning);
guitar.restoreTuningFromMemento(careTaker.getMemento());
System.out.println("Current tuning is: " + guitar.tuning);
}
}
// The Memento
data class TuningMemento(val tuning: String)
// The Originator
class Guitar {
var tuning: String = ""
set(value) {
field = value
println("Tuning set to: $value")
}
fun saveTuningToMemento(): TuningMemento {
println("Saving tuning to Memento.")
return TuningMemento(tuning)
}
fun restoreTuningFromMemento(memento: TuningMemento) {
tuning = memento.tuning
println("Restoring tuning from Memento: $tuning")
}
}
// The Caretaker
class TuningCareTaker {
var memento: TuningMemento? = null
}
// Client
fun main() {
val guitar = Guitar()
val careTaker = TuningCareTaker()
guitar.tuning = "E Standard"
careTaker.memento = guitar.saveTuningToMemento()
guitar.tuning = "Drop D"
println("Current tuning is: ${guitar.tuning}")
guitar.restoreTuningFromMemento(careTaker.memento!!)
println("Current tuning is: ${guitar.tuning}")
}
// The Memento
class TuningMemento {
private readonly tuning: string;
constructor(tuning: string) {
this.tuning = tuning;
}
public getTuning(): string {
return this.tuning;
}
}
// The Originator
class Guitar {
private tuning: string = "";
public setTuning(tuning: string): void {
this.tuning = tuning;
console.log("Tuning set to: " + tuning);
}
public saveTuningToMemento(): TuningMemento {
console.log("Saving tuning to Memento.");
return new TuningMemento(this.tuning);
}
public restoreTuningFromMemento(memento: TuningMemento): void {
this.tuning = memento.getTuning();
console.log("Restoring tuning from Memento: " + this.tuning);
}
}
// The Caretaker
class TuningCareTaker {
private memento: TuningMemento | undefined;
public setMemento(memento: TuningMemento): void {
this.memento = memento;
}
public getMemento(): TuningMemento | undefined {
return this.memento;
}
}
// Client
function main() {
const guitar = new Guitar();
const careTaker = new TuningCareTaker();
guitar.setTuning("E Standard");
careTaker.setMemento(guitar.saveTuningToMemento());
guitar.setTuning("Drop D");
console.log("Current tuning is: " + guitar.tuning);
guitar.restoreTuningFromMemento(careTaker.getMemento()!);
console.log("Current tuning is: " + guitar.tuning);
}
// The Memento
class TuningMemento {
final String tuning;
TuningMemento(this.tuning);
String getTuning() {
return tuning;
}
}
// The Originator
class Guitar {
String tuning = "";
void setTuning(String tuning) {
this.tuning = tuning;
print("Tuning set to: $tuning");
}
TuningMemento saveTuningToMemento() {
print("Saving tuning to Memento.");
return TuningMemento(tuning);
}
void restoreTuningFromMemento(TuningMemento memento) {
tuning = memento.tuning;
print("Restoring tuning from Memento: $tuning");
}
}
// The Caretaker
class TuningCareTaker {
TuningMemento? memento;
void setMemento(TuningMemento memento) {
this.memento = memento;
}
TuningMemento? getMemento() {
return memento;
}
}
// Client
void main() {
Guitar guitar = Guitar();
TuningCareTaker careTaker = TuningCareTaker();
guitar.setTuning("E Standard");
careTaker.setMemento(guitar.saveTuningToMemento());
guitar.setTuning("Drop D");
print("Current tuning is: ${guitar.tuning}");
guitar.restoreTuningFromMemento(careTaker.getMemento()!);
print("Current tuning is: ${guitar.tuning}");
}
// The Memento
class TuningMemento {
let tuning: String
init(tuning: String) {
self.tuning = tuning
}
func getTuning() -> String {
return tuning
}
}
// The Originator
class Guitar {
var tuning: String = "" {
didSet {
print("Tuning set to: \(tuning)")
}
}
func saveTuningToMemento() -> TuningMemento {
print("Saving tuning to Memento.")
return TuningMemento(tuning: tuning)
}
func restoreTuningFromMemento(memento: TuningMemento) {
tuning = memento.getTuning()
print("Restoring tuning from Memento: \(tuning)")
}
}
// The Caretaker
class TuningCareTaker {
var memento: TuningMemento?
func setMemento(memento: TuningMemento) {
self.memento = memento
}
func getMemento() -> TuningMemento? {
return memento
}
}
// Client
func main() {
let guitar = Guitar()
let careTaker = TuningCareTaker()
guitar.tuning = "E Standard"
careTaker.setMemento(memento: guitar.saveTuningToMemento())
guitar.tuning = "Drop D"
print("Current tuning is: \(guitar.tuning)")
guitar.restoreTuningFromMemento(memento: careTaker.getMemento()!)
print("Current tuning is: \(guitar.tuning)")
}
# The Memento
class TuningMemento:
def __init__(self, tuning):
self.tuning = tuning
def get_tuning(self):
return self.tuning
# The Originator
class Guitar:
def __init__(self):
self._tuning = ""
def set_tuning(self, tuning):
self._tuning = tuning
print(f"Tuning set to: {tuning}")
def save_tuning_to_memento(self):
print("Saving tuning to Memento.")
return TuningMemento(self._tuning)
def restore_tuning_from_memento(self, memento):
self._tuning = memento.get_tuning()
print(f"Restoring tuning from Memento: {self._tuning}")
@property
def tuning(self):
return self._tuning
# The Caretaker
class TuningCareTaker:
def __init__(self):
self._memento = None
def set_memento(self, memento):
self._memento = memento
def get_memento(self):
return self._memento
# Client
if __name__ == "__main__":
guitar = Guitar()
care_taker = TuningCareTaker()
guitar.set_tuning("E Standard")
care_taker.set_memento(guitar.save_tuning_to_memento())
guitar.set_tuning("Drop D")
print(f"Current tuning is: {guitar.tuning}")
guitar.restore_tuning_from_memento(care_taker.get_memento())
print(f"Current tuning is: {guitar.tuning}")
Summary#
The Memento pattern allows you to capture and externalize an object's internal state so that the object can be restored to this state later without violating encapsulation. It is useful for implementing undo mechanisms, or for saving and restoring the state of an object.
The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Key Concepts#
- Subject: Maintains a list of observers, provides methods to add and remove observers, and notifies observers of state changes.
- Observer: Defines an updating interface for objects that should be notified of changes in a subject.
- Concrete Subject: Stores the state of interest, sends notifications to observers when its state changes.
- Concrete Observer: Implements the Observer interface to receive notifications and update its state accordingly.
Benefits#
- Establishes a one-to-many relationship between objects.
- Decouples the subject from its observers.
- Provides a flexible way to add or remove observers.
The following diagram illustrates the Observer pattern with our guitar signal example, showing how Guitar notifies multiple effects automatically when the signal changes:
classDiagram
class EffectObserver {
<<interface>>
+update(String)
}
class GuitarSignal {
<<interface>>
+attach(EffectObserver)
+detach(EffectObserver)
+notifyObservers()
}
class Guitar {
-observers: List~EffectObserver~
-signal: String
+getSignal() String
+setSignal(String)
+attach(EffectObserver)
+detach(EffectObserver)
+notifyObservers()
}
class ChorusEffect {
+update(String)
}
class DelayEffect {
+update(String)
}
GuitarSignal <|.. Guitar
EffectObserver <|.. ChorusEffect
EffectObserver <|.. DelayEffect
Guitar o-- EffectObserver
The Observer enables a publish-subscribe relationship where subjects notify all registered observers of state changes automatically. Let's see this notification mechanism in action.
Example#
Let's consider a scenario where we want to notify different guitar effects when the guitar's input signal changes.
import java.util.ArrayList;
import java.util.List;
// The Observer interface
interface EffectObserver {
void update(String signal);
}
// The Subject interface
interface GuitarSignal {
void attach(EffectObserver observer);
void detach(EffectObserver observer);
void notifyObservers();
}
// The Concrete Subject
class Guitar implements GuitarSignal {
private List<EffectObserver> observers = new ArrayList<>();
private String signal;
public String getSignal() {
return signal;
}
public void setSignal(String signal) {
this.signal = signal;
notifyObservers();
}
@Override
public void attach(EffectObserver observer) {
observers.add(observer);
}
@Override
public void detach(EffectObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (EffectObserver observer : observers) {
observer.update(signal);
}
}
}
// The Concrete Observers
class ChorusEffect implements EffectObserver {
@Override
public void update(String signal) {
System.out.println("Chorus Effect: Signal changed to " + signal + ". Applying chorus.");
}
}
class DelayEffect implements EffectObserver {
@Override
public void update(String signal) {
System.out.println("Delay Effect: Signal changed to " + signal + ". Applying delay.");
}
}
// Client
public class Main {
public static void main(String[] args) {
Guitar guitar = new Guitar();
ChorusEffect chorus = new ChorusEffect();
DelayEffect delay = new DelayEffect();
guitar.attach(chorus);
guitar.attach(delay);
guitar.setSignal("Clean");
guitar.setSignal("Distorted");
guitar.detach(chorus);
guitar.setSignal("Clean");
}
}
// The Observer interface
interface EffectObserver {
fun update(signal: String)
}
// The Subject interface
interface GuitarSignal {
fun attach(observer: EffectObserver)
fun detach(observer: EffectObserver)
fun notifyObservers()
}
// The Concrete Subject
class Guitar : GuitarSignal {
private val observers = mutableListOf<EffectObserver>()
var signal: String = ""
set(value) {
field = value
notifyObservers()
}
override fun attach(observer: EffectObserver) {
observers.add(observer)
}
override fun detach(observer: EffectObserver) {
observers.remove(observer)
}
override fun notifyObservers() {
observers.forEach { it.update(signal) }
}
}
// The Concrete Observers
class ChorusEffect : EffectObserver {
override fun update(signal: String) {
println("Chorus Effect: Signal changed to $signal. Applying chorus.")
}
}
class DelayEffect : EffectObserver {
override fun update(signal: String) {
println("Delay Effect: Signal changed to $signal. Applying delay.")
}
}
// Client
fun main() {
val guitar = Guitar()
val chorus = ChorusEffect()
val delay = DelayEffect()
guitar.attach(chorus)
guitar.attach(delay)
guitar.signal = "Clean"
guitar.signal = "Distorted"
guitar.detach(chorus)
guitar.signal = "Clean"
}
// The Observer interface
interface EffectObserver {
update(signal: string): void;
}
// The Subject interface
interface GuitarSignal {
attach(observer: EffectObserver): void;
detach(observer: EffectObserver): void;
notifyObservers(): void;
}
// The Concrete Subject
class Guitar implements GuitarSignal {
private observers: EffectObserver[] = [];
private _signal: string = "";
public get signal(): string {
return this._signal;
}
public set signal(signal: string) {
this._signal = signal;
this.notifyObservers();
}
public attach(observer: EffectObserver): void {
this.observers.push(observer);
}
public detach(observer: EffectObserver): void {
this.observers = this.observers.filter(obs => obs !== observer);
}
public notifyObservers(): void {
for (const observer of this.observers) {
observer.update(this.signal);
}
}
}
// The Concrete Observers
class ChorusEffect implements EffectObserver {
public update(signal: string): void {
console.log(`Chorus Effect: Signal changed to ${signal}. Applying chorus.`);
}
}
class DelayEffect implements EffectObserver {
public update(signal: string): void {
console.log(`Delay Effect: Signal changed to ${signal}. Applying delay.`);
}
}
// Client
function main() {
const guitar = new Guitar();
const chorus = new ChorusEffect();
const delay = new DelayEffect();
guitar.attach(chorus);
guitar.attach(delay);
guitar.signal = "Clean";
guitar.signal = "Distorted";
guitar.detach(chorus);
guitar.signal = "Clean";
}
// The Observer interface
abstract class EffectObserver {
void update(String signal);
}
// The Subject interface
abstract class GuitarSignal {
void attach(EffectObserver observer);
void detach(EffectObserver observer);
void notifyObservers();
}
// The Concrete Subject
class Guitar implements GuitarSignal {
List<EffectObserver> observers = [];
String _signal = "";
String get signal => _signal;
set signal(String signal) {
_signal = signal;
notifyObservers();
}
@override
void attach(EffectObserver observer) {
observers.add(observer);
}
@override
void detach(EffectObserver observer) {
observers.remove(observer);
}
@override
void notifyObservers() {
for (var observer in observers) {
observer.update(signal);
}
}
}
// The Concrete Observers
class ChorusEffect implements EffectObserver {
@override
void update(String signal) {
print("Chorus Effect: Signal changed to $signal. Applying chorus.");
}
}
class DelayEffect implements EffectObserver {
@override
void update(String signal) {
print("Delay Effect: Signal changed to $signal. Applying delay.");
}
}
// Client
void main() {
Guitar guitar = Guitar();
ChorusEffect chorus = ChorusEffect();
DelayEffect delay = DelayEffect();
guitar.attach(chorus);
guitar.attach(delay);
guitar.signal = "Clean";
guitar.signal = "Distorted";
guitar.detach(chorus);
guitar.signal = "Clean";
}
// The Observer protocol
protocol EffectObserver: AnyObject {
func update(signal: String)
}
// The Subject protocol
protocol GuitarSignal {
func attach(observer: EffectObserver)
func detach(observer: EffectObserver)
func notifyObservers()
}
// The Concrete Subject
class Guitar: GuitarSignal {
private var observers: [EffectObserver] = []
private var _signal: String = ""
var signal: String {
get {
return _signal
}
set {
_signal = newValue
notifyObservers()
}
}
func attach(observer: EffectObserver) {
observers.append(observer)
}
func detach(observer: EffectObserver) {
observers = observers.filter { $0 !== observer }
}
func notifyObservers() {
for observer in observers {
observer.update(signal: signal)
}
}
}
// The Concrete Observers
class ChorusEffect: EffectObserver {
func update(signal: String) {
print("Chorus Effect: Signal changed to \(signal). Applying chorus.")
}
}
class DelayEffect: EffectObserver {
func update(signal: String) {
print("Delay Effect: Signal changed to \(signal). Applying delay.")
}
}
// Client
func main() {
let guitar = Guitar()
let chorus = ChorusEffect()
let delay = DelayEffect()
guitar.attach(observer: chorus)
guitar.attach(observer: delay)
guitar.signal = "Clean"
guitar.signal = "Distorted"
guitar.detach(observer: chorus)
guitar.signal = "Clean"
}
# The Observer interface
class EffectObserver:
def update(self, signal):
raise NotImplementedError
# The Subject interface
class GuitarSignal:
def attach(self, observer):
raise NotImplementedError
def detach(self, observer):
raise NotImplementedError
def notify_observers(self):
raise NotImplementedError
# The Concrete Subject
class Guitar(GuitarSignal):
def __init__(self):
self._observers = []
self._signal = ""
@property
def signal(self):
return self._signal
@signal.setter
def signal(self, signal):
self._signal = signal
self.notify_observers()
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify_observers(self):
for observer in self._observers:
observer.update(self._signal)
# The Concrete Observers
class ChorusEffect(EffectObserver):
def update(self, signal):
print(f"Chorus Effect: Signal changed to {signal}. Applying chorus.")
class DelayEffect(EffectObserver):
def update(self, signal):
print(f"Delay Effect: Signal changed to {signal}. Applying delay.")
# Client
if __name__ == "__main__":
guitar = Guitar()
chorus = ChorusEffect()
delay = DelayEffect()
guitar.attach(chorus)
guitar.attach(delay)
guitar.signal = "Clean"
guitar.signal = "Distorted"
guitar.detach(chorus)
guitar.signal = "Clean"
Summary#
The Observer pattern defines a one-to-many dependency between objects, where a subject notifies its observers of any state changes. This pattern is useful for building decoupled systems where objects need to react to changes in other objects without being tightly coupled.
The State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is useful when an object's behavior depends on its state, and it must change its behavior at runtime depending on that state.
Key Concepts#
- Context: Defines the interface of interest to clients. Maintains an instance of a ConcreteState subclass that defines the current state.
- State: Defines an interface for encapsulating the behavior associated with a particular state of the Context.
- Concrete States: Each subclass implements a behavior associated with a state of the Context.
Benefits#
- Organizes state-specific behavior.
- Makes state transitions explicit.
- Adds or changes states easily.
The following diagram illustrates the State pattern with our amplifier example, showing how the Amplifier delegates behavior to different state objects that handle transitions:
classDiagram
class AmplifierState {
<<interface>>
+powerOn(Amplifier)
+powerOff(Amplifier)
+standby(Amplifier)
}
class OnState {
+powerOn(Amplifier)
+powerOff(Amplifier)
+standby(Amplifier)
}
class OffState {
+powerOn(Amplifier)
+powerOff(Amplifier)
+standby(Amplifier)
}
class StandbyState {
+powerOn(Amplifier)
+powerOff(Amplifier)
+standby(Amplifier)
}
class Amplifier {
-state: AmplifierState
+setState(AmplifierState)
+powerOn()
+powerOff()
+standby()
}
AmplifierState <|.. OnState
AmplifierState <|.. OffState
AmplifierState <|.. StandbyState
Amplifier o-- AmplifierState
The State pattern allows an object to change its behavior when its internal state changes, encapsulating state-specific logic in separate classes. Let's see these state transitions in action.
Example#
Let's consider a guitar amplifier that can be in different states (e.g., On, Off, Standby). The behavior of the amplifier changes based on its current state.
// The State interface
interface AmplifierState {
void powerOn(Amplifier amplifier);
void powerOff(Amplifier amplifier);
void standby(Amplifier amplifier);
}
// The Concrete states
class OnState implements AmplifierState {
@Override
public void powerOn(Amplifier amplifier) {
System.out.println("Amplifier is already ON.");
}
@Override
public void powerOff(Amplifier amplifier) {
System.out.println("Turning amplifier OFF.");
amplifier.setState(new OffState());
}
@Override
public void standby(Amplifier amplifier) {
System.out.println("Switching to Standby mode.");
amplifier.setState(new StandbyState());
}
}
class OffState implements AmplifierState {
@Override
public void powerOn(Amplifier amplifier) {
System.out.println("Turning amplifier ON.");
amplifier.setState(new OnState());
}
@Override
public void powerOff(Amplifier amplifier) {
System.out.println("Amplifier is already OFF.");
}
@Override
public void standby(Amplifier amplifier) {
System.out.println("Cannot switch to Standby mode when OFF.");
}
}
class StandbyState implements AmplifierState {
@Override
public void powerOn(Amplifier amplifier) {
System.out.println("Turning amplifier ON from Standby.");
amplifier.setState(new OnState());
}
@Override
public void powerOff(Amplifier amplifier) {
System.out.println("Turning amplifier OFF.");
amplifier.setState(new OffState());
}
@Override
public void standby(Amplifier amplifier) {
System.out.println("Amplifier is already in Standby mode.");
}
}
// The Context
class Amplifier {
private AmplifierState state;
public Amplifier() {
this.state = new OffState(); // Initial state
}
public void setState(AmplifierState state) {
this.state = state;
}
public void powerOn() {
state.powerOn(this);
}
public void powerOff() {
state.powerOff(this);
}
public void standby() {
state.standby(this);
}
public static void main(String[] args) {
Amplifier amplifier = new Amplifier();
amplifier.powerOn();
amplifier.standby();
amplifier.powerOff();
}
}
// The State interface
interface AmplifierState {
fun powerOn(amplifier: Amplifier)
fun powerOff(amplifier: Amplifier)
fun standby(amplifier: Amplifier)
}
// The Concrete states
class OnState : AmplifierState {
override fun powerOn(amplifier: Amplifier) {
println("Amplifier is already ON.")
}
override fun powerOff(amplifier: Amplifier) {
println("Turning amplifier OFF.")
amplifier.state = OffState()
}
override fun standby(amplifier: Amplifier) {
println("Switching to Standby mode.")
amplifier.state = StandbyState()
}
}
class OffState : AmplifierState {
override fun powerOn(amplifier: Amplifier) {
println("Turning amplifier ON.")
amplifier.state = OnState()
}
override fun powerOff(amplifier: Amplifier) {
println("Amplifier is already OFF.")
}
override fun standby(amplifier: Amplifier) {
println("Cannot switch to Standby mode when OFF.")
}
}
class StandbyState : AmplifierState {
override fun powerOn(amplifier: Amplifier) {
println("Turning amplifier ON from Standby.")
amplifier.state = OnState()
}
override fun powerOff(amplifier: Amplifier) {
println("Turning amplifier OFF.")
amplifier.state = OffState()
}
override fun standby(amplifier: Amplifier) {
println("Amplifier is already in Standby mode.")
}
}
// The Context
class Amplifier {
var state: AmplifierState = OffState() // Initial state
fun powerOn() {
state.powerOn(this)
}
fun powerOff() {
state.powerOff(this)
}
fun standby() {
state.standby(this)
}
}
fun main() {
val amplifier = Amplifier()
amplifier.powerOn()
amplifier.standby()
amplifier.powerOff()
}
// The State interface
interface AmplifierState {
powerOn(amplifier: Amplifier): void;
powerOff(amplifier: Amplifier): void;
standby(amplifier: Amplifier): void;
}
// The Concrete states
class OnState implements AmplifierState {
powerOn(amplifier: Amplifier): void {
console.log("Amplifier is already ON.");
}
powerOff(amplifier: Amplifier): void {
console.log("Turning amplifier OFF.");
amplifier.setState(new OffState());
}
standby(amplifier: Amplifier): void {
console.log("Switching to Standby mode.");
amplifier.setState(new StandbyState());
}
}
class OffState implements AmplifierState {
powerOn(amplifier: Amplifier): void {
console.log("Turning amplifier ON.");
amplifier.setState(new OnState());
}
powerOff(amplifier: Amplifier): void {
console.log("Amplifier is already OFF.");
}
standby(amplifier: Amplifier): void {
console.log("Cannot switch to Standby mode when OFF.");
}
}
class StandbyState implements AmplifierState {
powerOn(amplifier: Amplifier): void {
console.log("Turning amplifier ON from Standby.");
amplifier.setState(new OnState());
}
powerOff(amplifier: Amplifier): void {
console.log("Turning amplifier OFF.");
amplifier.setState(new OffState());
}
standby(amplifier: Amplifier): void {
console.log("Amplifier is already in Standby mode.");
}
}
// The Context
class Amplifier {
private state: AmplifierState = new OffState(); // Initial state
public setState(state: AmplifierState): void {
this.state = state;
}
public powerOn(): void {
this.state.powerOn(this);
}
public powerOff(): void {
this.state.powerOff(this);
}
public standby(): void {
this.state.standby(this);
}
}
// Client
const amplifier = new Amplifier();
amplifier.powerOn();
amplifier.standby();
amplifier.powerOff();
// The State interface
abstract class AmplifierState {
void powerOn(Amplifier amplifier);
void powerOff(Amplifier amplifier);
void standby(Amplifier amplifier);
}
// The Concrete states
class OnState implements AmplifierState {
@override
void powerOn(Amplifier amplifier) {
print("Amplifier is already ON.");
}
@override
void powerOff(Amplifier amplifier) {
print("Turning amplifier OFF.");
amplifier.setState(OffState());
}
@override
void standby(Amplifier amplifier) {
print("Switching to Standby mode.");
amplifier.setState(StandbyState());
}
}
class OffState implements AmplifierState {
@override
void powerOn(Amplifier amplifier) {
print("Turning amplifier ON.");
amplifier.setState(OnState());
}
@override
void powerOff(Amplifier amplifier) {
print("Amplifier is already OFF.");
}
@override
void standby(Amplifier amplifier) {
print("Cannot switch to Standby mode when OFF.");
}
}
class StandbyState implements AmplifierState {
@override
void powerOn(Amplifier amplifier) {
print("Turning amplifier ON from Standby.");
amplifier.setState(OnState());
}
@override
void powerOff(Amplifier amplifier) {
print("Turning amplifier OFF.");
amplifier.setState(OffState());
}
@override
void standby(Amplifier amplifier) {
print("Amplifier is already in Standby mode.");
}
}
// The Context
class Amplifier {
AmplifierState state = OffState(); // Initial state
void setState(AmplifierState state) {
this.state = state;
}
void powerOn() {
state.powerOn(this);
}
void powerOff() {
state.powerOff(this);
}
void standby() {
state.standby(this);
}
}
void main() {
Amplifier amplifier = Amplifier();
amplifier.powerOn();
amplifier.standby();
amplifier.powerOff();
}
// The State protocol
protocol AmplifierState {
func powerOn(amplifier: Amplifier)
func powerOff(amplifier: Amplifier)
func standby(amplifier: Amplifier)
}
// The Concrete states
class OnState: AmplifierState {
func powerOn(amplifier: Amplifier) {
print("Amplifier is already ON.")
}
func powerOff(amplifier: Amplifier) {
print("Turning amplifier OFF.")
amplifier.state = OffState()
}
func standby(amplifier: Amplifier) {
print("Switching to Standby mode.")
amplifier.state = StandbyState()
}
}
class OffState: AmplifierState {
func powerOn(amplifier: Amplifier) {
print("Turning amplifier ON.")
amplifier.state = OnState()
}
func powerOff(amplifier: Amplifier) {
print("Amplifier is already OFF.")
}
func standby(amplifier: Amplifier) {
print("Cannot switch to Standby mode when OFF.")
}
}
class StandbyState: AmplifierState {
func powerOn(amplifier: Amplifier) {
print("Turning amplifier ON from Standby.")
amplifier.state = OnState()
}
func powerOff(amplifier: Amplifier) {
print("Turning amplifier OFF.")
amplifier.state = OffState()
}
func standby(amplifier: Amplifier) {
print("Amplifier is already in Standby mode.")
}
}
// The Context
class Amplifier {
var state: AmplifierState = OffState() // Initial state
func setState(state: AmplifierState) {
self.state = state
}
func powerOn() {
state.powerOn(amplifier: self)
}
func powerOff() {
state.powerOff(amplifier: self)
}
func standby() {
state.standby(amplifier: self)
}
}
// Client
let amplifier = Amplifier()
amplifier.powerOn()
amplifier.standby()
amplifier.powerOff()
# The State interface
class AmplifierState:
def power_on(self, amplifier):
pass
def power_off(self, amplifier):
pass
def standby(self, amplifier):
pass
# The Concrete states
class OnState(AmplifierState):
def power_on(self, amplifier):
print("Amplifier is already ON.")
def power_off(self, amplifier):
print("Turning amplifier OFF.")
amplifier.state = OffState()
def standby(self, amplifier):
print("Switching to Standby mode.")
amplifier.state = StandbyState()
class OffState(AmplifierState):
def power_on(self, amplifier):
print("Turning amplifier ON.")
amplifier.state = OnState()
def power_off(self, amplifier):
print("Amplifier is already OFF.")
def standby(self, amplifier):
print("Cannot switch to Standby mode when OFF.")
class StandbyState(AmplifierState):
def power_on(self, amplifier):
print("Turning amplifier ON from Standby.")
amplifier.state = OnState()
def power_off(self, amplifier):
print("Turning amplifier OFF.")
amplifier.state = OffState()
def standby(self, amplifier):
print("Amplifier is already in Standby mode.")
# The Context
class Amplifier:
def __init__(self):
self.state = OffState() # Initial state
def set_state(self, state):
self.state = state
def power_on(self):
self.state.power_on(self)
def power_off(self):
self.state.power_off(self)
def standby(self):
self.state.standby(self)
# Client
amplifier = Amplifier()
amplifier.power_on()
amplifier.standby()
amplifier.power_off()
Summary#
The State pattern allows an object to alter its behavior when its internal state changes. It encapsulates state-specific behavior into separate classes, making it easier to add new states and modify existing ones.
The Strategy pattern is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable. The strategy pattern allows you to select an algorithm at runtime.
Key Concepts#
- Strategy: Declares an interface common to all supported algorithms. The Context uses this interface to call the algorithm defined by a Concrete Strategy.
- Concrete Strategy: Implements the algorithm using the Strategy interface.
- Context: Is configured with a Concrete Strategy object. Maintains a reference to a Strategy object. May define an interface that lets the Strategy access its data.
Benefits#
- Defines a family of algorithms.
- Provides a substitute for subclassing.
- Adds new strategies without changing the context.
The following diagram illustrates the Strategy pattern with our guitar tuning example, showing how different tuning algorithms can be swapped interchangeably at runtime:
classDiagram
class TuningStrategy {
<<interface>>
+tune(Guitar)
}
class ChromaticTuning {
+tune(Guitar)
}
class StandardETuning {
+tune(Guitar)
}
class DropDTuning {
+tune(Guitar)
}
class Guitar {
-tuningStrategy: TuningStrategy
+setTuningStrategy(TuningStrategy)
+tune()
}
TuningStrategy <|.. ChromaticTuning
TuningStrategy <|.. StandardETuning
TuningStrategy <|.. DropDTuning
Guitar o-- TuningStrategy
The Strategy enables selecting algorithms at runtime, encapsulating each algorithm in its own class and making them interchangeable. Let's see this flexibility in action.
Example#
Let's consider a guitar tuner application that can use different tuning algorithms (e.g., Chromatic, Standard E, Drop D). The tuning algorithm can be selected at runtime.
// The Strategy interface
interface TuningStrategy {
void tune(Guitar guitar);
}
// The Concrete strategies
class ChromaticTuning implements TuningStrategy {
@Override
public void tune(Guitar guitar) {
System.out.println("Tuning guitar chromatically.");
// Implementation for chromatic tuning
}
}
class StandardETuning implements TuningStrategy {
@Override
public void tune(Guitar guitar) {
System.out.println("Tuning guitar to Standard E.");
// Implementation for Standard E tuning
}
}
class DropDTuning implements TuningStrategy {
@Override
public void tune(Guitar guitar) {
System.out.println("Tuning guitar to Drop D.");
// Implementation for Drop D tuning
}
}
// The Context
class Guitar {
private TuningStrategy tuningStrategy;
public Guitar(TuningStrategy tuningStrategy) {
this.tuningStrategy = tuningStrategy;
}
public void setTuningStrategy(TuningStrategy tuningStrategy) {
this.tuningStrategy = tuningStrategy;
}
public void tune() {
tuningStrategy.tune(this);
}
public static void main(String[] args) {
Guitar guitar = new Guitar(new StandardETuning());
guitar.tune();
guitar.setTuningStrategy(new DropDTuning());
guitar.tune();
}
}
// The Strategy interface
interface TuningStrategy {
fun tune(guitar: Guitar)
}
// The Concrete strategies
class ChromaticTuning : TuningStrategy {
override fun tune(guitar: Guitar) {
println("Tuning guitar chromatically.")
// Implementation for chromatic tuning
}
}
class StandardETuning : TuningStrategy {
override fun tune(guitar: Guitar) {
println("Tuning guitar to Standard E.")
// Implementation for Standard E tuning
}
}
class DropDTuning : TuningStrategy {
override fun tune(guitar: Guitar) {
println("Tuning guitar to Drop D.")
// Implementation for Drop D tuning
}
}
// The Context
class Guitar(private var tuningStrategy: TuningStrategy) {
fun setTuningStrategy(tuningStrategy: TuningStrategy) {
this.tuningStrategy = tuningStrategy
}
fun tune() {
tuningStrategy.tune(this)
}
}
fun main() {
val guitar = Guitar(StandardETuning())
guitar.tune()
guitar.setTuningStrategy(DropDTuning())
guitar.tune()
}
// The Strategy interface
interface TuningStrategy {
tune(guitar: Guitar): void;
}
// The Concrete strategies
class ChromaticTuning implements TuningStrategy {
tune(guitar: Guitar): void {
console.log("Tuning guitar chromatically.");
// Implementation for chromatic tuning
}
}
class StandardETuning implements TuningStrategy {
tune(guitar: Guitar): void {
console.log("Tuning guitar to Standard E.");
// Implementation for Standard E tuning
}
}
class DropDTuning implements TuningStrategy {
tune(guitar: Guitar): void {
console.log("Tuning guitar to Drop D.");
// Implementation for Drop D tuning
}
}
// The Context
class Guitar {
private tuningStrategy: TuningStrategy;
constructor(tuningStrategy: TuningStrategy) {
this.tuningStrategy = tuningStrategy;
}
public setTuningStrategy(tuningStrategy: TuningStrategy): void {
this.tuningStrategy = tuningStrategy;
}
public tune(): void {
this.tuningStrategy.tune(this);
}
}
// Client
const guitar = new Guitar(new StandardETuning());
guitar.tune();
guitar.setTuningStrategy(new DropDTuning());
guitar.tune();
// The Strategy interface
abstract class TuningStrategy {
void tune(Guitar guitar);
}
// The Concrete strategies
class ChromaticTuning implements TuningStrategy {
@override
void tune(Guitar guitar) {
print("Tuning guitar chromatically.");
// Implementation for chromatic tuning
}
}
class StandardETuning implements TuningStrategy {
@override
void tune(Guitar guitar) {
print("Tuning guitar to Standard E.");
// Implementation for Standard E tuning
}
}
class DropDTuning implements TuningStrategy {
@override
void tune(Guitar guitar) {
print("Tuning guitar to Drop D.");
// Implementation for Drop D tuning
}
}
// The Context
class Guitar {
TuningStrategy tuningStrategy;
Guitar(this.tuningStrategy);
void setTuningStrategy(TuningStrategy tuningStrategy) {
this.tuningStrategy = tuningStrategy;
}
void tune() {
tuningStrategy.tune(this);
}
}
void main() {
Guitar guitar = Guitar(StandardETuning());
guitar.tune();
guitar.setTuningStrategy(DropDTuning());
guitar.tune();
}
// The Strategy protocol
protocol TuningStrategy {
func tune(guitar: Guitar)
}
// The Concrete strategies
class ChromaticTuning: TuningStrategy {
func tune(guitar: Guitar) {
print("Tuning guitar chromatically.")
// Implementation for chromatic tuning
}
}
class StandardETuning: TuningStrategy {
func tune(guitar: Guitar) {
print("Tuning guitar to Standard E.")
// Implementation for Standard E tuning
}
}
class DropDTuning: TuningStrategy {
func tune(guitar: Guitar) {
print("Tuning guitar to Drop D.")
// Implementation for Drop D tuning
}
}
// The Context
class Guitar {
var tuningStrategy: TuningStrategy
init(tuningStrategy: TuningStrategy) {
self.tuningStrategy = tuningStrategy
}
func setTuningStrategy(tuningStrategy: TuningStrategy) {
self.tuningStrategy = tuningStrategy
}
func tune() {
tuningStrategy.tune(guitar: self)
}
}
// Client
let guitar = Guitar(tuningStrategy: StandardETuning())
guitar.tune()
guitar.setTuningStrategy(tuningStrategy: DropDTuning())
guitar.tune()
# The Strategy interface
class TuningStrategy:
def tune(self, guitar):
pass
# The Concrete strategies
class ChromaticTuning(TuningStrategy):
def tune(self, guitar):
print("Tuning guitar chromatically.")
# Implementation for chromatic tuning
class StandardETuning(TuningStrategy):
def tune(self, guitar):
print("Tuning guitar to Standard E.")
# Implementation for Standard E tuning
class DropDTuning(TuningStrategy):
def tune(self, guitar):
print("Tuning guitar to Drop D.")
# Implementation for Drop D tuning
# The Context
class Guitar:
def __init__(self, tuning_strategy):
self.tuning_strategy = tuning_strategy
def set_tuning_strategy(self, tuning_strategy):
self.tuning_strategy = tuning_strategy
def tune(self):
self.tuning_strategy.tune(self)
# Client
guitar = Guitar(StandardETuning())
guitar.tune()
guitar.set_tuning_strategy(DropDTuning())
guitar.tune()
Summary#
The Strategy pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
Key Concepts#
- Abstract Class: Defines the template method that contains the skeleton of the algorithm.
- Concrete Class: Implements the abstract methods to provide specific behavior.
Benefits#
- Promotes code reuse.
- Allows subclasses to redefine certain steps of an algorithm without changing the algorithm's structure.
- Provides a common interface for different implementations.
The following diagram illustrates the Template Method pattern with our guitar building example, showing how the algorithm structure is defined in the base class while subclasses provide specific implementations:
classDiagram
class GuitarBuilder {
<<abstract>>
+buildGuitar()
+selectWood()*
+applyFinish()*
+addHardware()*
+setupStrings()*
+finalAssembly()
}
class StratocasterBuilder {
+selectWood()
+applyFinish()
+addHardware()
+setupStrings()
}
class LesPaulBuilder {
+selectWood()
+applyFinish()
+addHardware()
+setupStrings()
}
GuitarBuilder <|-- StratocasterBuilder
GuitarBuilder <|-- LesPaulBuilder
The Template Method defines the skeleton of an algorithm, deferring specific steps to subclasses while maintaining the overall structure. Let's see this structure in action.
Example#
Let's consider a guitar building process where the basic steps are the same, but specific details like wood selection or finish vary.
// Abstract class defining the template method
abstract class GuitarBuilder {
// Template method
public final void buildGuitar() {
selectWood();
applyFinish();
addHardware();
setupStrings();
finalAssembly();
}
// Abstract methods to be implemented by subclasses
abstract void selectWood();
abstract void applyFinish();
abstract void addHardware();
abstract void setupStrings();
// Concrete method
void finalAssembly() {
System.out.println("Final assembly and quality check.");
}
}
// Concrete class for building a Stratocaster
class StratocasterBuilder extends GuitarBuilder {
@Override
void selectWood() {
System.out.println("Selecting Alder wood for Stratocaster body.");
}
@Override
void applyFinish() {
System.out.println("Applying Sunburst finish to Stratocaster.");
}
@Override
void addHardware() {
System.out.println("Adding Stratocaster hardware (pickups, bridge, tuners).");
}
@Override
void setupStrings() {
System.out.println("Setting up strings for Stratocaster.");
}
}
// Concrete class for building a Les Paul
class LesPaulBuilder extends GuitarBuilder {
@Override
void selectWood() {
System.out.println("Selecting Mahogany wood with Maple top for Les Paul body.");
}
@Override
void applyFinish() {
System.out.println("Applying Cherry Sunburst finish to Les Paul.");
}
@Override
void addHardware() {
System.out.println("Adding Les Paul hardware (pickups, bridge, tuners).");
}
@Override
void setupStrings() {
System.out.println("Setting up strings for Les Paul.");
}
}
// Client
public class Main {
public static void main(String[] args) {
GuitarBuilder stratBuilder = new StratocasterBuilder();
stratBuilder.buildGuitar();
System.out.println("---");
GuitarBuilder lesPaulBuilder = new LesPaulBuilder();
lesPaulBuilder.buildGuitar();
}
}
// Abstract class defining the template method
abstract class GuitarBuilder {
// Template method
fun buildGuitar() {
selectWood()
applyFinish()
addHardware()
setupStrings()
finalAssembly()
}
// Abstract methods to be implemented by subclasses
abstract fun selectWood()
abstract fun applyFinish()
abstract fun addHardware()
abstract fun setupStrings()
// Concrete method
fun finalAssembly() {
println("Final assembly and quality check.")
}
}
// Concrete class for building a Stratocaster
class StratocasterBuilder : GuitarBuilder() {
override fun selectWood() {
println("Selecting Alder wood for Stratocaster body.")
}
override fun applyFinish() {
println("Applying Sunburst finish to Stratocaster.")
}
override fun addHardware() {
println("Adding Stratocaster hardware (pickups, bridge, tuners).")
}
override fun setupStrings() {
println("Setting up strings for Stratocaster.")
}
}
// Concrete class for building a Les Paul
class LesPaulBuilder : GuitarBuilder() {
override fun selectWood() {
println("Selecting Mahogany wood with Maple top for Les Paul body.")
}
override fun applyFinish() {
println("Applying Cherry Sunburst finish to Les Paul.")
}
override fun addHardware() {
println("Adding Les Paul hardware (pickups, bridge, tuners).")
}
override fun setupStrings() {
println("Setting up strings for Les Paul.")
}
}
// Client
fun main() {
val stratBuilder = StratocasterBuilder()
stratBuilder.buildGuitar()
println("---")
val lesPaulBuilder = LesPaulBuilder()
lesPaulBuilder.buildGuitar()
}
// Abstract class defining the template method
abstract class GuitarBuilder {
// Template method
buildGuitar(): void {
this.selectWood();
this.applyFinish();
this.addHardware();
this.setupStrings();
this.finalAssembly();
}
// Abstract methods to be implemented by subclasses
abstract selectWood(): void;
abstract applyFinish(): void;
abstract addHardware(): void;
abstract setupStrings(): void;
// Concrete method
finalAssembly(): void {
console.log("Final assembly and quality check.");
}
}
// Concrete class for building a Stratocaster
class StratocasterBuilder extends GuitarBuilder {
selectWood(): void {
console.log("Selecting Alder wood for Stratocaster body.");
}
applyFinish(): void {
console.log("Applying Sunburst finish to Stratocaster.");
}
addHardware(): void {
console.log("Adding Stratocaster hardware (pickups, bridge, tuners).");
}
setupStrings(): void {
console.log("Setting up strings for Stratocaster.");
}
}
// Concrete class for building a Les Paul
class LesPaulBuilder extends GuitarBuilder {
selectWood(): void {
console.log("Selecting Mahogany wood with Maple top for Les Paul body.");
}
applyFinish(): void {
console.log("Applying Cherry Sunburst finish to Les Paul.");
}
addHardware(): void {
console.log("Adding Les Paul hardware (pickups, bridge, tuners).");
}
setupStrings(): void {
console.log("Setting up strings for Les Paul.");
}
}
// Client
const stratBuilder = new StratocasterBuilder();
stratBuilder.buildGuitar();
console.log("---");
const lesPaulBuilder = new LesPaulBuilder();
lesPaulBuilder.buildGuitar();
// Abstract class defining the template method
abstract class GuitarBuilder {
// Template method
void buildGuitar() {
selectWood();
applyFinish();
addHardware();
setupStrings();
finalAssembly();
}
// Abstract methods to be implemented by subclasses
void selectWood();
void applyFinish();
void addHardware();
void setupStrings();
// Concrete method
void finalAssembly() {
print("Final assembly and quality check.");
}
}
// Concrete class for building a Stratocaster
class StratocasterBuilder extends GuitarBuilder {
@override
void selectWood() {
print("Selecting Alder wood for Stratocaster body.");
}
@override
void applyFinish() {
print("Applying Sunburst finish to Stratocaster.");
}
@override
void addHardware() {
print("Adding Stratocaster hardware (pickups, bridge, tuners).");
}
@override
void setupStrings() {
print("Setting up strings for Stratocaster.");
}
}
// Concrete class for building a Les Paul
class LesPaulBuilder extends GuitarBuilder {
@override
void selectWood() {
print("Selecting Mahogany wood with Maple top for Les Paul body.");
}
@override
void applyFinish() {
print("Applying Cherry Sunburst finish to Les Paul.");
}
@override
void addHardware() {
print("Adding Les Paul hardware (pickups, bridge, tuners).");
}
@override
void setupStrings() {
print("Setting up strings for Les Paul.");
}
}
// Client
void main() {
GuitarBuilder stratBuilder = StratocasterBuilder();
stratBuilder.buildGuitar();
print("---");
GuitarBuilder lesPaulBuilder = LesPaulBuilder();
lesPaulBuilder.buildGuitar();
}
// Abstract class defining the template method
class GuitarBuilder {
// Template method
func buildGuitar() {
selectWood()
applyFinish()
addHardware()
setupStrings()
finalAssembly()
}
// Abstract methods to be implemented by subclasses
func selectWood() {
fatalError("Must override")
}
func applyFinish() {
fatalError("Must override")
}
func addHardware() {
fatalError("Must override")
}
func setupStrings() {
fatalError("Must override")
}
// Concrete method
func finalAssembly() {
print("Final assembly and quality check.")
}
}
// Concrete class for building a Stratocaster
class StratocasterBuilder: GuitarBuilder {
override func selectWood() {
print("Selecting Alder wood for Stratocaster body.")
}
override func applyFinish() {
print("Applying Sunburst finish to Stratocaster.")
}
override func addHardware() {
print("Adding Stratocaster hardware (pickups, bridge, tuners).")
}
override func setupStrings() {
print("Setting up strings for Stratocaster.")
}
}
// Concrete class for building a Les Paul
class LesPaulBuilder: GuitarBuilder {
override func selectWood() {
print("Selecting Mahogany wood with Maple top for Les Paul body.")
}
override func applyFinish() {
print("Applying Cherry Sunburst finish to Les Paul.")
}
override func addHardware() {
print("Adding Les Paul hardware (pickups, bridge, tuners).")
}
override func setupStrings() {
print("Setting up strings for Les Paul.")
}
}
// Client
let stratBuilder = StratocasterBuilder()
stratBuilder.buildGuitar()
print("---")
let lesPaulBuilder = LesPaulBuilder()
lesPaulBuilder.buildGuitar()
# Abstract class defining the template method
class GuitarBuilder:
# Template method
def build_guitar(self):
self.select_wood()
self.apply_finish()
self.add_hardware()
self.setup_strings()
self.final_assembly()
# Abstract methods to be implemented by subclasses
def select_wood(self):
raise NotImplementedError
def apply_finish(self):
raise NotImplementedError
def add_hardware(self):
raise NotImplementedError
def setup_strings(self):
raise NotImplementedError
# Concrete method
def final_assembly(self):
print("Final assembly and quality check.")
# Concrete class for building a Stratocaster
class StratocasterBuilder(GuitarBuilder):
def select_wood(self):
print("Selecting Alder wood for Stratocaster body.")
def apply_finish(self):
print("Applying Sunburst finish to Stratocaster.")
def add_hardware(self):
print("Adding Stratocaster hardware (pickups, bridge, tuners).")
def setup_strings(self):
print("Setting up strings for Stratocaster.")
# Concrete class for building a Les Paul
class LesPaulBuilder(GuitarBuilder):
def select_wood(self):
print("Selecting Mahogany wood with Maple top for Les Paul body.")
def apply_finish(self):
print("Applying Cherry Sunburst finish to Les Paul.")
def add_hardware(self):
print("Adding Les Paul hardware (pickups, bridge, tuners).")
def setup_strings(self):
print("Setting up strings for Les Paul.")
# Client
strat_builder = StratocasterBuilder()
strat_builder.build_guitar()
print("---")
les_paul_builder = LesPaulBuilder()
les_paul_builder.build_guitar()
Summary#
The Template Method pattern provides a way to define the skeleton of an algorithm in a superclass, allowing subclasses to override specific steps without changing the algorithm's structure.
The Visitor pattern is a behavioral design pattern that lets you add new operations to objects without modifying the classes of those objects. It involves separating the algorithm from the object structure on which it operates.
Key Concepts#
- Visitor: Defines a visit operation for each type of Concrete Element.
- Concrete Visitor: Implements each visit operation defined in the Visitor.
- Element: Defines an accept operation that takes a visitor as an argument.
- Concrete Element: Implements the accept operation to call the corresponding visit operation on the visitor.
- Object Structure: A collection of Elements that can be traversed.
Benefits#
- Adds new operations to objects without modifying their classes.
- Centralizes operations on related objects.
- Promotes loose coupling between objects and operations.
The following diagram illustrates the Visitor pattern with our guitar maintenance example, showing how different visitors perform operations on guitar parts through double dispatch:
classDiagram
class GuitarPart {
<<interface>>
+accept(GuitarPartVisitor)
}
class GuitarBody {
+accept(GuitarPartVisitor)
+getName() String
}
class GuitarNeck {
+accept(GuitarPartVisitor)
+getName() String
}
class GuitarStrings {
+accept(GuitarPartVisitor)
+getName() String
}
class GuitarPartVisitor {
<<interface>>
+visit(GuitarBody)
+visit(GuitarNeck)
+visit(GuitarStrings)
}
class CleaningVisitor {
+visit(GuitarBody)
+visit(GuitarNeck)
+visit(GuitarStrings)
}
class TuningVisitor {
+visit(GuitarBody)
+visit(GuitarNeck)
+visit(GuitarStrings)
}
GuitarPart <|.. GuitarBody
GuitarPart <|.. GuitarNeck
GuitarPart <|.. GuitarStrings
GuitarPartVisitor <|.. CleaningVisitor
GuitarPartVisitor <|.. TuningVisitor
GuitarPart ..> GuitarPartVisitor : accepts
The Visitor separates algorithms from the objects they operate on, enabling you to add new operations without modifying element classes. Let's see this separation in action.
Example#
Let's consider a guitar maintenance scenario where different maintenance tasks (e.g., cleaning, tuning, restringing) need to be performed on different guitar parts (e.g., body, neck, strings).
// The Element interface
interface GuitarPart {
void accept(GuitarPartVisitor visitor);
}
// Concrete elements
class GuitarBody implements GuitarPart {
@Override
public void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
public String getName() {
return "Guitar Body";
}
}
class GuitarNeck implements GuitarPart {
@Override
public void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
public String getName() {
return "Guitar Neck";
}
}
class GuitarStrings implements GuitarPart {
@Override
public void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
public String getName() {
return "Guitar Strings";
}
}
// The Visitor interface
interface GuitarPartVisitor {
void visit(GuitarBody body);
void visit(GuitarNeck neck);
void visit(GuitarStrings strings);
}
// Concrete visitors
class CleaningVisitor implements GuitarPartVisitor {
@Override
public void visit(GuitarBody body) {
System.out.println("Cleaning " + body.getName());
}
@Override
public void visit(GuitarNeck neck) {
System.out.println("Cleaning " + neck.getName());
}
@Override
public void visit(GuitarStrings strings) {
System.out.println("Cleaning " + strings.getName());
}
}
class TuningVisitor implements GuitarPartVisitor {
@Override
public void visit(GuitarBody body) {
// No action for body
}
@Override
public void visit(GuitarNeck neck) {
// No action for neck
}
@Override
public void visit(GuitarStrings strings) {
System.out.println("Tuning " + strings.getName());
}
}
class RestringingVisitor implements GuitarPartVisitor {
@Override
public void visit(GuitarBody body) {
// No action for body
}
@Override
public void visit(GuitarNeck neck) {
// No action for neck
}
@Override
public void visit(GuitarStrings strings) {
System.out.println("Restringing " + strings.getName());
}
}
// Client
public class Main {
public static void main(String[] args) {
GuitarPart body = new GuitarBody();
GuitarPart neck = new GuitarNeck();
GuitarPart strings = new GuitarStrings();
GuitarPartVisitor cleaningVisitor = new CleaningVisitor();
GuitarPartVisitor tuningVisitor = new TuningVisitor();
GuitarPartVisitor restringingVisitor = new RestringingVisitor();
body.accept(cleaningVisitor);
neck.accept(cleaningVisitor);
strings.accept(cleaningVisitor);
strings.accept(tuningVisitor);
strings.accept(restringingVisitor);
}
}
// The Element interface
interface GuitarPart {
fun accept(visitor: GuitarPartVisitor)
}
// Concrete elements
class GuitarBody : GuitarPart {
override fun accept(visitor: GuitarPartVisitor) {
visitor.visit(this)
}
fun getName(): String {
return "Guitar Body"
}
}
class GuitarNeck : GuitarPart {
override fun accept(visitor: GuitarPartVisitor) {
visitor.visit(this)
}
fun getName(): String {
return "Guitar Neck"
}
}
class GuitarStrings : GuitarPart {
override fun accept(visitor: GuitarPartVisitor) {
visitor.visit(this)
}
fun getName(): String {
return "Guitar Strings"
}
}
// The Visitor interface
interface GuitarPartVisitor {
fun visit(body: GuitarBody)
fun visit(neck: GuitarNeck)
fun visit(strings: GuitarStrings)
}
// Concrete visitors
class CleaningVisitor : GuitarPartVisitor {
override fun visit(body: GuitarBody) {
println("Cleaning ${body.getName()}")
}
override fun visit(neck: GuitarNeck) {
println("Cleaning ${neck.getName()}")
}
override fun visit(strings: GuitarStrings) {
println("Cleaning ${strings.getName()}")
}
}
class TuningVisitor : GuitarPartVisitor {
override fun visit(body: GuitarBody) {
// No action for body
}
override fun visit(neck: GuitarNeck) {
// No action for neck
}
override fun visit(strings: GuitarStrings) {
println("Tuning ${strings.getName()}")
}
}
class RestringingVisitor : GuitarPartVisitor {
override fun visit(body: GuitarBody) {
// No action for body
}
override fun visit(neck: GuitarNeck) {
// No action for neck
}
override fun visit(strings: GuitarStrings) {
println("Restringing ${strings.getName()}")
}
}
// Client
fun main() {
val body = GuitarBody()
val neck = GuitarNeck()
val strings = GuitarStrings()
val cleaningVisitor = CleaningVisitor()
val tuningVisitor = TuningVisitor()
val restringingVisitor = RestringingVisitor()
body.accept(cleaningVisitor)
neck.accept(cleaningVisitor)
strings.accept(cleaningVisitor)
strings.accept(tuningVisitor)
strings.accept(restringingVisitor)
}
// The Element interface
interface GuitarPart {
accept(visitor: GuitarPartVisitor): void;
}
// Concrete elements
class GuitarBody implements GuitarPart {
accept(visitor: GuitarPartVisitor): void {
visitor.visit(this);
}
getName(): string {
return "Guitar Body";
}
}
class GuitarNeck implements GuitarPart {
accept(visitor: GuitarPartVisitor): void {
visitor.visit(this);
}
getName(): string {
return "Guitar Neck";
}
}
class GuitarStrings implements GuitarPart {
accept(visitor: GuitarPartVisitor): void {
visitor.visit(this);
}
getName(): string {
return "Guitar Strings";
}
}
// The Visitor interface
interface GuitarPartVisitor {
visit(body: GuitarBody): void;
visit(neck: GuitarNeck): void;
visit(strings: GuitarStrings): void;
}
// Concrete visitors
class CleaningVisitor implements GuitarPartVisitor {
visit(body: GuitarBody): void {
console.log("Cleaning " + body.getName());
}
visit(neck: GuitarNeck): void {
console.log("Cleaning " + neck.getName());
}
visit(strings: GuitarStrings): void {
console.log("Cleaning " + strings.getName());
}
}
class TuningVisitor implements GuitarPartVisitor {
visit(body: GuitarBody): void {
// No action for body
}
visit(neck: GuitarNeck): void {
// No action for neck
}
visit(strings: GuitarStrings): void {
console.log("Tuning " + strings.getName());
}
}
class RestringingVisitor implements GuitarPartVisitor {
visit(body: GuitarBody): void {
// No action for body
}
visit(neck: GuitarNeck): void {
// No action for neck
}
visit(strings: GuitarStrings): void {
console.log("Restringing " + strings.getName());
}
}
// Client
const body = new GuitarBody();
const neck = new GuitarNeck();
const strings = new GuitarStrings();
const cleaningVisitor = new CleaningVisitor();
const tuningVisitor = new TuningVisitor();
const restringingVisitor = new RestringingVisitor();
body.accept(cleaningVisitor);
neck.accept(cleaningVisitor);
strings.accept(cleaningVisitor);
strings.accept(tuningVisitor);
strings.accept(restringingVisitor);
// The Element interface
abstract class GuitarPart {
void accept(GuitarPartVisitor visitor);
}
// Concrete elements
class GuitarBody implements GuitarPart {
@override
void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
String getName() {
return "Guitar Body";
}
}
class GuitarNeck implements GuitarPart {
@override
void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
String getName() {
return "Guitar Neck";
}
}
class GuitarStrings implements GuitarPart {
@override
void accept(GuitarPartVisitor visitor) {
visitor.visit(this);
}
String getName() {
return "Guitar Strings";
}
}
// The Visitor interface
abstract class GuitarPartVisitor {
void visit(GuitarBody body);
void visit(GuitarNeck neck);
void visit(GuitarStrings strings);
}
// Concrete visitors
class CleaningVisitor implements GuitarPartVisitor {
@override
void visit(GuitarBody body) {
print("Cleaning ${body.getName()}");
}
@override
void visit(GuitarNeck neck) {
print("Cleaning ${neck.getName()}");
}
@override
void visit(GuitarStrings strings) {
print("Cleaning ${strings.getName()}");
}
}
class TuningVisitor implements GuitarPartVisitor {
@override
void visit(GuitarBody body) {
// No action for body
}
@override
void visit(GuitarNeck neck) {
// No action for neck
}
@override
void visit(GuitarStrings strings) {
print("Tuning ${strings.getName()}");
}
}
class RestringingVisitor implements GuitarPartVisitor {
@override
void visit(GuitarBody body) {
// No action for body
}
@override
void visit(GuitarNeck neck) {
// No action for neck
}
@override
void visit(GuitarStrings strings) {
print("Restringing ${strings.getName()}");
}
}
// Client
void main() {
GuitarPart body = GuitarBody();
GuitarPart neck = GuitarNeck();
GuitarPart strings = GuitarStrings();
GuitarPartVisitor cleaningVisitor = CleaningVisitor();
GuitarPartVisitor tuningVisitor = TuningVisitor();
GuitarPartVisitor restringingVisitor = RestringingVisitor();
body.accept(cleaningVisitor);
neck.accept(cleaningVisitor);
strings.accept(cleaningVisitor);
strings.accept(tuningVisitor);
strings.accept(restringingVisitor);
}
// The Element protocol
protocol GuitarPart {
func accept(visitor: GuitarPartVisitor)
}
// Concrete elements
class GuitarBody: GuitarPart {
func accept(visitor: GuitarPartVisitor) {
visitor.visit(body: self)
}
func getName() -> String {
return "Guitar Body"
}
}
class GuitarNeck: GuitarPart {
func accept(visitor: GuitarPartVisitor) {
visitor.visit(neck: self)
}
func getName() -> String {
return "Guitar Neck"
}
}
class GuitarStrings: GuitarPart {
func accept(visitor: GuitarPartVisitor) {
visitor.visit(strings: self)
}
func getName() -> String {
return "Guitar Strings"
}
}
// The Visitor protocol
protocol GuitarPartVisitor {
func visit(body: GuitarBody)
func visit(neck: GuitarNeck)
func visit(strings: GuitarStrings)
}
// Concrete visitors
class CleaningVisitor: GuitarPartVisitor {
func visit(body: GuitarBody) {
print("Cleaning \(body.getName())")
}
func visit(neck: GuitarNeck) {
print("Cleaning \(neck.getName())")
}
func visit(strings: GuitarStrings) {
print("Cleaning \(strings.getName())")
}
}
class TuningVisitor: GuitarPartVisitor {
func visit(body: GuitarBody) {
// No action for body
}
func visit(neck: GuitarNeck) {
// No action for neck
}
func visit(strings: GuitarStrings) {
print("Tuning \(strings.getName())")
}
}
class RestringingVisitor: GuitarPartVisitor {
func visit(body: GuitarBody) {
// No action for body
}
func visit(neck: GuitarNeck) {
// No action for neck
}
func visit(strings: GuitarStrings) {
print("Restringing \(strings.getName())")
}
}
// Client
let body = GuitarBody()
let neck = GuitarNeck()
let strings = GuitarStrings()
let cleaningVisitor = CleaningVisitor()
let tuningVisitor = TuningVisitor()
let restringingVisitor = RestringingVisitor()
body.accept(visitor: cleaningVisitor)
neck.accept(visitor: cleaningVisitor)
strings.accept(visitor: cleaningVisitor)
strings.accept(visitor: tuningVisitor)
strings.accept(visitor: restringingVisitor)
# The Element interface
class GuitarPart:
def accept(self, visitor):
pass
# Concrete elements
class GuitarBody(GuitarPart):
def accept(self, visitor):
visitor.visit_body(self)
def get_name(self):
return "Guitar Body"
class GuitarNeck(GuitarPart):
def accept(self, visitor):
visitor.visit_neck(self)
def get_name(self):
return "Guitar Neck"
class GuitarStrings(GuitarPart):
def accept(self, visitor):
visitor.visit_strings(self)
def get_name(self):
return "Guitar Strings"
# The Visitor interface
class GuitarPartVisitor:
def visit_body(self, body):
pass
def visit_neck(self, neck):
pass
def visit_strings(self, strings):
pass
# Concrete visitors
class CleaningVisitor(GuitarPartVisitor):
def visit_body(self, body):
print(f"Cleaning {body.get_name()}")
def visit_neck(self, neck):
print(f"Cleaning {neck.get_name()}")
def visit_strings(self, strings):
print(f"Cleaning {strings.get_name()}")
class TuningVisitor(GuitarPartVisitor):
def visit_body(self, body):
pass # No action for body
def visit_neck(self, neck):
pass # No action for neck
def visit_strings(self, strings):
print(f"Tuning {strings.get_name()}")
class RestringingVisitor(GuitarPartVisitor):
def visit_body(self, body):
pass # No action for body
def visit_neck(self, neck):
pass # No action for neck
def visit_strings(self, strings):
print(f"Restringing {strings.get_name()}")
# Client
body = GuitarBody()
neck = GuitarNeck()
strings = GuitarStrings()
cleaning_visitor = CleaningVisitor()
tuning_visitor = TuningVisitor()
restringing_visitor = RestringingVisitor()
body.accept(cleaning_visitor)
neck.accept(cleaning_visitor)
strings.accept(cleaning_visitor)
strings.accept(tuning_visitor)
strings.accept(restringing_visitor)
Summary#
The Visitor pattern allows you to add new operations to objects without modifying their classes by separating the algorithm from the object structure.