Behavioral Design Patterns
Behavioral Design Patterns
Drawbacks:
Performance: If there are many handlers, it can lead to performance overhead as the
request may need to be passed through many handlers.
Unpredictability: It can sometimes be difficult to predict which handler will process
the request, especially in large chains.
Example of Chain of Responsibility Pattern in Java:
In this example, let's say we have a system that processes different levels of support tickets.
Each handler in the chain represents a different level of support (e.g., JuniorSupport,
SeniorSupport, ManagerSupport), and each will handle requests according to their level.
Java Code Example:
// Handler Interface
interface SupportHandler {
void handleRequest(String request);
void setNext(SupportHandler nextHandler); // Set the next handler in the chain
}
@Override
public void handleRequest(String request) {
if (request.equals("Basic")) {
System.out.println("JuniorSupport: Handling basic request");
} else if (nextHandler != null) {
nextHandler.handleRequest(request); // Pass the request to the next handler
}
}
@Override
public void setNext(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
@Override
public void handleRequest(String request) {
if (request.equals("Advanced")) {
System.out.println("SeniorSupport: Handling advanced request");
} else if (nextHandler != null) {
nextHandler.handleRequest(request); // Pass the request to the next handler
}
}
@Override
public void setNext(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
@Override
public void handleRequest(String request) {
if (request.equals("Critical")) {
System.out.println("ManagerSupport: Handling critical request");
} else {
System.out.println("ManagerSupport: Unable to handle request");
}
}
@Override
public void setNext(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
// Client Code
public class ChainOfResponsibilityExample {
public static void main(String[] args) {
// Create the handlers (support levels)
SupportHandler juniorSupport = new JuniorSupport();
SupportHandler seniorSupport = new SeniorSupport();
SupportHandler managerSupport = new ManagerSupport();
3. Client: In the main method, the client creates the chain of handlers and makes various
requests. Each handler in the chain will process the request according to its ability or
pass it to the next handler.
When multiple objects can handle a request: If you have several objects that can
handle a particular request, but you don't know in advance which one will handle it.
When you want to decouple the sender and receiver: The sender doesn't need to know
which handler will process the request, making the system more flexible.
When the responsibility of handling requests can be passed dynamically: For example,
different levels of a customer service team handling different types of support
requests.
Real-World Examples:
Event handling in GUI frameworks: Events like mouse clicks or key presses can be
passed through a chain of event listeners.
Logging systems: Loggers can be organized in a chain where each logger handles
different levels of log messages (e.g., DEBUG, INFO, ERROR).
Validation systems: Multiple validation rules can be chained together, with each rule
handling different parts of the validation process.
COMMAND PATTERN DESIGN
The Command Pattern is a behavioral design pattern that encapsulates a request as an
object, thereby allowing users to parameterize clients with queues, requests, and operations.
It decouples the sender of a request from its receiver, allowing for more flexibility in
handling requests. This pattern is particularly useful for undo/redo functionality, task
scheduling, and request queuing.
@Override
public void execute() {
light.turnOn();
}
}
@Override
public void execute() {
light.turnOff();
}
}
// Client code
public class CommandPatternExample {
public static void main(String[] args) {
// Receiver: The light
Light livingRoomLight = new Light();
// Terminal Expressions
class Number implements Expression {
private int number;
@Override
public int interpret() {
return number;
}
}
// Non-Terminal Expressions
class Add implements Expression {
private Expression left;
private Expression right;
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
// Client code
public class InterpreterPatternExample {
public static void main(String[] args) {
// Creating the expression "5 + 3 - 2"
Expression five = new Number(5);
Expression three = new Number(3);
Expression two = new Number(2);
Drawbacks:
Overhead: If not carefully designed, it might introduce additional complexity or
overhead, especially with large collections.
Performance: Iteration could become less efficient if not implemented properly for
specific data structures.
// Iterator Interface
interface Iterator {
boolean hasNext();
Object next();
}
@Override
public boolean hasNext() {
return position < books.length && books[position] != null;
}
@Override
public Object next() {
if (this.hasNext()) {
return books[position++];
}
return null;
}
}
// ConcreteAggregate: A specific collection for Books
class BookShelf implements BookCollection {
private Book[] books;
private int numberOfBooks = 0;
@Override
public Iterator createIterator() {
return new BookIterator(books);
}
}
// Client code
public class IteratorPatternExample {
public static void main(String[] args) {
// Create a collection (BookShelf) and add books to it
BookShelf bookShelf = new BookShelf(5);
bookShelf.addBook(new Book("Design Patterns"));
bookShelf.addBook(new Book("Clean Code"));
bookShelf.addBook(new Book("The Pragmatic Programmer"));
2. ConcreteMediator:
o The concrete mediator class implements the mediator interface and coordinates
communication between the various colleague objects.
3. Colleague:
o Colleague objects are the participants in the system. They don’t communicate
directly with each other but instead communicate through the mediator.
4. Client:
o The client initializes the mediator and colleagues and triggers the
communication through the mediator.
How It Works:
1. The colleague objects send their requests or messages to the mediator rather than
communicating with each other directly.
2. The mediator receives the request and decides which colleague object needs to be
notified based on the request.
3. The mediator updates the relevant colleagues and facilitates the communication.
Drawbacks:
Single Point of Failure: If the mediator becomes too complex, it can become a
bottleneck and a single point of failure.
Overhead: The pattern introduces another layer of indirection (mediator) which can
increase complexity in certain cases.
// Mediator Interface
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// ConcreteMediator: ChatRoom
class ChatRoom implements ChatMediator {
private List<User> users;
public ChatRoom() {
this.users = new ArrayList<>();
}
@Override
public void sendMessage(String message, User user) {
for (User u : users) {
// Don't send the message to the user who sent it
if (u != user) {
u.receiveMessage(message);
}
}
}
@Override
public void addUser(User user) {
this.users.add(user);
}
}
// Colleague: User
abstract class User {
protected ChatMediator mediator;
protected String name;
@Override
public void sendMessage(String message) {
System.out.println(name + " sends message: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " received message: " + message);
}
}
// Client code
public class MediatorPatternExample {
public static void main(String[] args) {
ChatMediator chatRoom = new ChatRoom();
chatRoom.addUser(user1);
chatRoom.addUser(user2);
chatRoom.addUser(user3);
How It Works:
1. The Subject maintains a list of Observers that have registered interest in changes to
the subject's state.
2. When the state of the Subject changes, it notifies all its Observers by calling their
update() method.
3. Each Observer updates its state based on the new state of the Subject.
Drawbacks:
Memory Leaks: If observers are not properly removed from the subject's list, it can
lead to memory leaks.
Performance Issues: If there are a large number of observers, notifying them can
result in performance issues.
// Observer Interface
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// ConcreteSubject: WeatherData
class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// ConcreteObserver: CurrentConditionsDisplay
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
private Subject weatherData;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
// ConcreteObserver: StatisticsDisplay
class StatisticsDisplay implements Observer {
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
public void display() {
System.out.println("Statistics: " + temperature + "F degrees, " + humidity + "% humidity, " +
pressure + " pressure.");
}
}
// Client code
public class ObserverPatternExample {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// Create observers
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
How It Works:
1. The Context class holds a reference to the current state.
2. The State interface defines methods for handling different behaviors, which are
implemented by concrete state classes.
3. The Context delegates the request to the current state.
4. The state classes implement the state-specific behavior. The Context can switch
between states by updating its reference to the appropriate state object.
Drawbacks:
Increased number of classes: You end up creating many state classes, which may
increase the number of classes in the system.
Complexity in small systems: For simple systems with minimal state changes, the state
pattern might add unnecessary complexity.
@Override
public void insertQuarter() {
System.out.println("You inserted a quarter.");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter.");
}
@Override
public void turnCrank() {
System.out.println("You turned the crank but there's no quarter.");
}
@Override
public void dispense() {
System.out.println("You need to insert a quarter first.");
}
}
@Override
public void insertQuarter() {
System.out.println("You can't insert another quarter.");
}
@Override
public void ejectQuarter() {
System.out.println("Quarter returned.");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("You turned the crank.");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
@Override
public void dispense() {
System.out.println("No gumball dispensed.");
}
}
@Override
public void insertQuarter() {
System.out.println("Sorry, the machine is sold out.");
}
@Override
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter.");
}
@Override
public void turnCrank() {
System.out.println("The machine is sold out, no gumball dispensed.");
}
@Override
public void dispense() {
System.out.println("No gumball dispensed.");
}
}
public GumballMachine() {
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldOutState = new SoldOutState(this);
currentState = noQuarterState;
}
// Client code
public class StatePatternExample {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
Drawbacks:
Increased number of classes: The pattern may increase the number of classes,
especially if there are many different algorithms.
Complexity: In small systems with few algorithms, the strategy pattern may add
unnecessary complexity.
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card: " + cardNumber);
}
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal: " + email);
}
}
// Context Class
class PaymentContext {
private PaymentStrategy paymentStrategy;
// Client Code
public class StrategyPatternExample {
public static void main(String[] args) {
// Payment through CreditCard
PaymentContext paymentContext = new PaymentContext(new CreditCardPayment("1234-5678-
9876-5432"));
paymentContext.executePayment(500);