Chapter #5 Behavioural Design Patterns — Software Design and Architecture Specialization University of Alberta
Behavioral design patterns in software engineering focus on the interactions and responsibilities among objects, emphasizing how individual objects collaborate to achieve a common goal. These patterns are essential for effective software design, as they ensure that each object has a distinct purpose within the larger solution. Common examples of behavioral design patterns include:
- Chain of Responsibility Pattern: This pattern creates a chain of receiver objects for a request. Each receiver contains logic that decides whether to process the request or pass it to the next receiver in the chain.
- Command Pattern: The command pattern encapsulates a request as an object, thereby allowing parameterization of clients with queues, requests, and operations. It enables the parameterization of clients with queued requests.
- Interpreter Pattern: The interpreter pattern defines a grammatical representation for a language and an interpreter to interpret the grammar. It is used to define the grammar for simple languages, protocols, or as a component in a larger system.
- Iterator Pattern: The iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It separates the algorithm from the aggregate object, simplifying the process of traversing the elements of a collection.
- Mediator Pattern: This pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly and allows for the actions of various objects to be coordinated through the mediator.
- Memento Pattern: The memento pattern captures and externalizes an object’s internal state so that the object can be restored to this state later. It allows you to restore an object to its previous state without revealing its implementation details.
- Observer Pattern: The observer pattern defines a one-to-many dependency between objects, ensuring that when one object changes state, all its dependents are notified and updated automatically.
- State Pattern: This pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
- Strategy Pattern: The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the algorithm to vary independently from the clients that use it.
- Template Method Pattern: The template method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
Template Method Pattern
This situation could be modeled with a class that has a method that makes the recipe for each subclass, spaghetti with meatballs or penne alfredo. The method knows the general set of steps to make either dish — from boiling water to adding the garnish. Steps that are special to a dish, like the sauce to add, are implemented in the subclass. The UML diagram below illustrates this example.
public abstract class PastaDish {
final void makeRecipe() {
boilWater();
addPasta();
cookPasta();
drainAndPlate();
addSauce();
addProtein();
addGarnish();
}
abstract void addPasta();
abstract void addSauce();
abstract void addProtein();
abstract void addGarnish();
private void boilWater() {
System.out.println("Boiling water");
}
public class SpaghettiMeatballs extends PastaDish {
public void addPasta() {
System.out.println("Add spaghetti");
}
public void addProtein() {
System.out.println("Add meatballs");
}
public void addSauce() {
System.out.println("Add tomato sauce");
}
public void addGarnish() {
System.out.println("Add Parmesan cheese");
} }
public class PenneAlfredo extends PastaDish {
public void addPasta() {
System.out.println("Add penne");
}
public void addProtein() {
System.out.println("Add chicken");
}
public void addSauce() {
System.out.println("Add Alfredo sauce");
}
public void addGarnish() {
System.out.println("Add parsley");
} }
Chain of Responsibility Pattern
The Chain of Responsibility pattern is a behavioral design pattern where a chain of handler objects is responsible for handling requests. In software design, these objects are linked together. When a client object sends a request, the first handler in the chain attempts to process it. If the handler can handle the request, the process ends with this handler. However, if the handler is unable to process the request, the request is passed to the next handler in the chain. This process continues until a handler can effectively handle the request. This pattern promotes the concept of loose coupling, as the client is decoupled from the specific handling of the request, and the responsibility is distributed among the chain of handler objects.
State Pattern
The State design pattern enables objects in a codebase to be aware of their current state and to select an appropriate behavior based on this state. This pattern is especially useful when there is a need to modify an object’s behavior according to changes in its internal state during runtime. It proves beneficial in simplifying methods that have long conditionals depending on the object’s state. By encapsulating the behavior within state classes, the State pattern ensures that the object’s behavior can change dynamically as its state changes, leading to more maintainable and extensible code.
public interface State {
public void insertDollar( VendingMachine vendingMachine);
public void ejectMoney( VendingMachine vendingMachine );
public void dispense( VendingMachine vendingMachine );
}
public class IdleState implements State {
public void insertDollar( VendingMachine vendingMachine ) {
System.out.println( "dollar inserted" );
vendingMachine.setState(
vendingMachine.getHasOneDollarState()
); }
public void ejectMoney( VendingMachine vendingMachine ) {
System.out.println( "no money to return" );
}
public void dispense( VendingMachine vendingMachine ) {
System.out.println( "payment required" );
}
}
public class HasOneDollarState implements State {
public void insertDollar( VendingMachine vendingMachine ) {
System.out.println( "already have one dollar" );
}
public void ejectMoney( VendingMachine vendingMachine ) {
System.out.println( "returning money" );
vendingMachine.doReturnMoney();
vendingMachine.setState(
vendingMachine.getIdleState()
);
}
public void dispense( VendingMachine vendingMachine ) {
System.out.println( "releasing product" );
if (vendingMachine.getCount() > 1) {
vendingMachine.doReleaseProduct();
vendingMachine.setState(vendingMachine.getIdleState());
} else {
vendingMachine.doReleaseProduct();
vendingMachine.setState(
vendingMachine.getOutOfStockState());
}
public class PopMachine {
private State idleState;
private State hasOneDollarState;
private State outOfStockState;
private State currentState;
private int count;
public PopMachine( int count ) {
// make the needed states
idleState = new IdleState();
hasOneDollarState = new HasOneDollarState();
outOfStockState = new OutOfStockState();
if (count > 0) {
currentState = idleState;
this.count = count;
} else {
currentState = outOfStockState;
this.count = 0;
} }
public void insertDollar() {
currentState.insertDollar( this );
}
public void ejectMoney() {
currentState.ejectMoney( this );
}
public void dispense() {
currentState.dispense( this );
}
Command Pattern
The Command design pattern encapsulates a request as an object, creating a separation between the sender and receiver objects. Normally, in direct communication, the sender object calls a method of the receiver object to perform an action. However, in the command pattern, a command object is created between the sender and receiver. This design allows the sender to remain unaware of the receiver and the specific methods to call. An invoker object is required to execute the command object and instruct the designated receiver object to carry out the intended task. The invoker acts as an intermediary, invoking the command objects to fulfill their respective tasks. Additionally, a command manager can be employed to keep track of commands, manipulate them, and invoke their execution as needed. This pattern facilitates decoupling and flexibility in the execution of requests, contributing to more maintainable and extensible code.
public class PasteCommand extends Command {
private Document document;
private int position;
private String text;
...
public PasteCommand( Document document,
int position, String text ){
this.document = document;
this.position = position;
this.text = =text;
}
public void execute() {
document.insertText ( position, text );
}
public void unexecute() {
document.deleteText ( position, text.length());
}
public boolean isReversible() {
return true;
} }
CommandManager commandManager = CommandManager.getInstance();
Command command = new PasteCommand(aDocument, aPosition,
AText);
commandManager.invokeCommand (command);
Observer Pattern
The Observer design pattern involves a subject that maintains a list of observers. Observers rely on the subject to notify them of any changes to the subject’s state. Typically, the pattern includes a superclass with an attribute that tracks all observers, as well as an interface with a method that enables an observer to be notified of any state changes to the subject. The subject superclass might also have subclasses that implement the Observer interface, establishing the relationship between the subject and the observer. To illustrate, consider a scenario where you have subscribed to a blog and wish to receive notifications whenever changes are made to the blog. In this context, the blog serves as the subject, and your subscription represents an observer that relies on the blog to receive updates about any modifications made to its content.
public class Subject {
private ArrayList<Observer> observers = new
ArrayList<Observer>();
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void unregisterObserver(Observer observer) {
}
public void notify() {
for (Observer o : observers) {
o.update();
} }
public interface Observer {
public void update();
}
class Subscriber implements Observer {
public void update() {
// get the blog change
...
} }
Ibrahim Can Erdoğan