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 design pattern helps you pass requests along a chain of handlers where each handler decides whether to process the request or pass it along. Instead of coupling the sender to a specific receiver, you build a chain of potential handlers. This keeps the sender decoupled from the receivers and allows you to change the chain dynamically. As a result, you can add, remove, or reorder handlers without modifying the client code.
Key Concepts
- Handler: The interface that defines a method for handling requests and a reference to the next handler.
- Concrete Handler: The class that implements the handler interface and decides whether to handle or pass the request.
- Client: The class that initiates the request and relies on the chain to process it.
Benefits
- It reduces coupling between the sender and receiver of a request.
- It adds flexibility in assigning responsibilities dynamically.
Let's make the Chain of Responsibility pattern more concrete with a practical example. Imagine we have a guitar setup process where different technicians handle different aspects—strings, intonation, action—based on their expertise. By using the Chain of Responsibility, each handler processes what it can and passes the rest along.
The diagram below illustrates how the Chain of Responsibility pattern is applied in our guitar setup scenario: each handler (StringSetupHandler, IntonationSetupHandler) either processes the request or passes it to the next handler in the chain:
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
Now, let's see how this pattern comes to life in code by implementing a guitar setup chain where each handler processes specific setup tasks.
// 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 design pattern helps you turn requests into stand-alone objects that contain all information about the request. Instead of calling methods directly, you encapsulate the request in a command object. This keeps the invoker decoupled from the receiver and allows you to queue, log, or undo operations. As a result, you can parameterize objects with operations and support reversible actions.
Key Concepts
- Command: The interface that declares the execute method for all commands.
- Concrete Command: The class that binds a receiver to an action and implements execute by invoking operations on the receiver.
- Invoker: The class that asks the command to carry out the request.
- Receiver: The class that knows how to perform the actual operations.
Benefits
- It decouples objects that invoke operations from those that perform them.
- It supports undoable operations and command queuing.
Let's make the Command pattern more concrete with a practical example. Imagine we have guitar effects that we want to apply and undo. By using the Command, we can encapsulate each effect application as a command object that knows how to execute and reverse itself.
The diagram below illustrates how the Command pattern is applied in our effects scenario: the ApplyEffectCommand encapsulates the action of applying an effect, and the GuitarEffectInvoker triggers commands without knowing the details:
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
Now, let's see how this pattern comes to life in code by implementing guitar effect commands that can be executed and 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 design pattern helps you access elements of a collection sequentially without exposing its underlying representation. Instead of embedding traversal logic in the collection, you extract it into a separate iterator object. This keeps the collection's interface clean and allows multiple traversal methods. As a result, you can iterate over different data structures using a uniform interface.
Key Concepts
- Iterator: The interface that defines methods for accessing and traversing elements.
- Concrete Iterator: The class that implements the Iterator interface and tracks the current position.
- Aggregate: The interface that defines a method for creating an Iterator.
- Concrete Aggregate: The class that implements the Aggregate interface and returns a Concrete Iterator.
Benefits
- It simplifies the collection interface by extracting traversal logic.
- It supports multiple simultaneous traversals on the same collection.
Let's make the Iterator pattern more concrete with a practical example. Imagine we have a collection of guitar pedals that we want to traverse. By using the Iterator, we can access each pedal sequentially without knowing how the collection stores them internally.
The diagram below illustrates how the Iterator pattern is applied in our pedals scenario: the GuitarPedalCollection creates a PedalIterator that provides sequential access to pedals through a standard interface:
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
Now, let's see how this pattern comes to life in code by implementing an iterator for a guitar pedal collection.
// 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 design pattern helps you reduce coupling between objects by centralizing their communication in a mediator object. Instead of objects communicating directly with each other, they send messages through the mediator. This keeps objects loosely coupled since they only know about the mediator, not each other. As a result, you can change how objects interact by modifying the mediator without touching the objects themselves.
Key Concepts
- Mediator: The interface that defines communication methods for Colleague objects.
- Concrete Mediator: The class that implements the Mediator interface and coordinates communication between Colleagues.
- Colleague: The interface for objects that communicate through the Mediator.
- Concrete Colleague: The class that implements the Colleague interface and uses the Mediator for communication.
Benefits
- It reduces coupling between objects by eliminating direct references.
- It centralizes control over communication, making interactions easier to understand and modify.
Let's make the Mediator pattern more concrete with a practical example. Imagine we have different guitar effects (chorus, delay, reverb) that need to coordinate with each other. By using the Mediator, effects can communicate without knowing about each other directly.
The diagram below illustrates how the Mediator pattern is applied in our effects scenario: the GuitarEffectMediator coordinates communication between ChorusEffect, DelayEffect, and ReverbEffect colleagues:
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
Now, let's see how this pattern comes to life in code by implementing a mediator that coordinates guitar effects.
// 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 design pattern helps you capture and restore an object's internal state without violating encapsulation. Instead of exposing internal details, the object creates a memento that stores a snapshot of its state. This keeps the object's internals private while enabling state restoration. As a result, you can implement undo mechanisms or save checkpoints without breaking encapsulation.
Key Concepts
- Memento: The object that stores a snapshot of the Originator's internal state.
- Originator: The object that creates mementos and uses them to restore its state.
- Caretaker: The object that holds mementos without examining their contents.
Benefits
- It provides a way to restore an object to a previous state.
- It preserves encapsulation by not exposing the object's internal details.
Let's make the Memento pattern more concrete with a practical example. Imagine we have a guitar with tuning settings that we want to save and restore. By using the Memento, we can capture the current tuning state and restore it later without exposing the guitar's internal representation.
The diagram below illustrates how the Memento pattern is applied in our tuning scenario: the Guitar creates TuningMemento objects to save its state, and the TuningCareTaker stores them for later restoration:
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
Now, let's see how this pattern comes to life in code by implementing a guitar tuning system with save and restore capabilities.
// 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 design pattern helps you define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically. Instead of having objects poll for changes, the subject pushes updates to interested observers. This keeps subjects and observers loosely coupled. As a result, you can add or remove observers dynamically without modifying the subject.
Key Concepts
- Subject: The object that maintains a list of observers and notifies them of state changes.
- Observer: The interface that defines the update method for receiving notifications.
- Concrete Subject: The class that stores state and sends notifications when it changes.
- Concrete Observer: The class that implements the Observer interface and reacts to notifications.
Benefits
- It establishes a flexible one-to-many relationship between objects.
- It decouples subjects from their observers, allowing independent evolution.
Let's make the Observer pattern more concrete with a practical example. Imagine we have a guitar that sends signals to multiple effects. By using the Observer, effects can subscribe to signal changes and react automatically when the guitar's signal changes.
The diagram below illustrates how the Observer pattern is applied in our guitar scenario: the Guitar subject notifies all registered EffectObserver objects (ChorusEffect, DelayEffect) when its 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
Now, let's see how this pattern comes to life in code by implementing a guitar signal system that notifies effects of 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 design pattern helps you allow an object to alter its behavior when its internal state changes. Instead of using conditional statements to handle different states, you encapsulate state-specific behavior in separate state objects. This keeps state logic organized and makes transitions explicit. As a result, the object appears to change its class when its state changes, and adding new states doesn't require modifying existing code.
Key Concepts
- Context: The object that maintains a reference to the current state and delegates behavior to it.
- State: The interface that defines behavior associated with a particular state.
- Concrete States: The classes that implement state-specific behavior.
Benefits
- It organizes state-specific behavior into separate classes.
- It makes state transitions explicit and easier to understand.
Let's make the State pattern more concrete with a practical example. Imagine we have a guitar amplifier that can be in different states: On, Off, or Standby. By using the State pattern, each state handles its own behavior and transitions to other states.
The diagram below illustrates how the State pattern is applied in our amplifier scenario: the Amplifier delegates operations to the current AmplifierState, and each concrete state (OnState, OffState, StandbyState) handles 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
Now, let's see how this pattern comes to life in code by implementing an amplifier with different power states.
// 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 design pattern helps you define a family of algorithms, encapsulate each one in its own class, and make them interchangeable. Instead of hardcoding an algorithm in a class, you extract it into a separate strategy object. This keeps the context class independent of the algorithm implementation. As a result, you can switch algorithms at runtime and add new ones without modifying the context.
Key Concepts
- Strategy: The interface that declares the algorithm method used by the Context.
- Concrete Strategy: The class that implements a specific algorithm.
- Context: The class that uses a Strategy and can switch between different strategies at runtime.
Benefits
- It defines a family of interchangeable algorithms.
- It allows adding new strategies without changing the context class.
Let's make the Strategy pattern more concrete with a practical example. Imagine we have a guitar tuner that supports different tuning algorithms like chromatic, standard E, and drop D. By using the Strategy, we can switch tuning algorithms at runtime without modifying the tuner.
The diagram below illustrates how the Strategy pattern is applied in our tuning scenario: the Guitar uses a TuningStrategy interface, and different implementations (ChromaticTuning, StandardETuning, DropDTuning) can be swapped 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
Now, let's see how this pattern comes to life in code by implementing a guitar tuner with interchangeable tuning strategies.
// 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 design pattern helps you define the skeleton of an algorithm in a base class while letting subclasses override specific steps. Instead of duplicating the algorithm structure in each subclass, you define it once in the superclass. This keeps the algorithm's structure in one place while allowing customization of individual steps. As a result, you can reuse the overall algorithm while varying specific details.
Key Concepts
- Abstract Class: The class that defines the template method containing the algorithm skeleton and abstract steps.
- Concrete Class: The class that implements the abstract steps to provide specific behavior.
Benefits
- It promotes code reuse by defining the algorithm structure once.
- It allows subclasses to customize specific steps without changing the overall algorithm.
Let's make the Template Method pattern more concrete with a practical example. Imagine we have a guitar building process where the steps are always the same (select wood, apply finish, add hardware, setup strings), but the specifics vary for different guitar types. By using the Template Method, we define the process once and let subclasses customize each step.
The diagram below illustrates how the Template Method pattern is applied in our guitar building scenario: the GuitarBuilder defines the algorithm skeleton with buildGuitar(), and subclasses like StratocasterBuilder and LesPaulBuilder implement the specific steps:
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
Now, let's see how this pattern comes to life in code by implementing a guitar building process with customizable steps.
// 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 design pattern helps you add new operations to objects without modifying their classes. Instead of embedding operations in the element classes, you define them in separate visitor objects. This keeps element classes focused on their data while visitors handle operations. As a result, you can add new operations by creating new visitors without changing the element hierarchy.
Key Concepts
- Visitor: The interface that declares visit methods for each element type.
- Concrete Visitor: The class that implements visit methods with specific operations.
- Element: The interface that declares an accept method taking a visitor.
- Concrete Element: The class that implements accept by calling the visitor's corresponding visit method.
Benefits
- It allows adding new operations without modifying element classes.
- It centralizes related operations in visitor classes, keeping element classes simple.
Let's make the Visitor pattern more concrete with a practical example. Imagine we have guitar parts (body, neck, strings) that need various maintenance operations (cleaning, tuning). By using the Visitor, we can add new maintenance operations without modifying the guitar part classes.
The diagram below illustrates how the Visitor pattern is applied in our guitar maintenance scenario: guitar parts accept visitors, and each visitor (CleaningVisitor, TuningVisitor) performs its specific operation on each part type:
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
Now, let's see how this pattern comes to life in code by implementing guitar maintenance visitors that operate on different guitar parts.
// 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.