Behavioral Design Patterns in Java
Behavioral design patterns focus on communication between objects, how they interact and fulfill their responsibilities. These patterns help define how objects should communicate and cooperate. Let’s deep dive into the Observer, Strategy, Command, and Template Method patterns.
1. Observer Pattern
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Implementation
- Example: A simple notification system.
import java.util.ArrayList;
import java.util.List;
// Observer interface
interface Observer {
void update(String message);
}
// Concrete Observer class
class User implements Observer {
private String name;
public User(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
// Subject interface
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Concrete Subject class
class NotificationSystem implements Subject {
private List<Observer> observers = new ArrayList<>();
private String message;
@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(message);
}
}
public void setMessage(String message) {
this.message = message;
notifyObservers();
}
}
// Client
public class ObserverPatternDemo {
public static void main(String[] args) {
NotificationSystem notificationSystem = new NotificationSystem();
User user1 = new User("Alice");
User user2 = new User("Bob");
notificationSystem.registerObserver(user1);
notificationSystem.registerObserver(user2);
notificationSystem.setMessage("New notification message!");
notificationSystem.removeObserver(user1);
notificationSystem.setMessage("Another notification message!");
}
}
2. Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Implementation
- Example: Payment methods in an e-commerce application.
// Strategy interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategy classes
class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal.");
}
}
// Context class
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// Client
public class StrategyPatternDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9012-3456"));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout(200);
}
}
3. Command Pattern
The Command pattern encapsulates a request as an object, thereby allowing for parameterization of clients with queues, requests, and operations. It also provides support for undoable operations.
Implementation
- Example: A remote control system.
// Command interface
interface Command {
void execute();
}
// Concrete Command classes
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
// Receiver class
class Light {
public void on() {
System.out.println("Light is ON");
}
public void off() {
System.out.println("Light is OFF");
}
}
// Invoker class
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// Client
public class CommandPatternDemo {
public static void main(String[] args) {
Light livingRoomLight = new Light();
Command lightOn = new LightOnCommand(livingRoomLight);
Command lightOff = new LightOffCommand(livingRoomLight);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();
remote.setCommand(lightOff);
remote.pressButton();
}
}
4. 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.
Implementation
- Example: A process for preparing different types of beverages.
// Abstract class
abstract class Beverage {
// Template method
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
// Concrete classes
class Tea extends Beverage {
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding Lemon");
}
}
class Coffee extends Beverage {
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
// Client
public class TemplateMethodPatternDemo {
public static void main(String[] args) {
Beverage tea = new Tea();
tea.prepareRecipe();
Beverage coffee = new Coffee();
coffee.prepareRecipe();
}
}
Summary
Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. They help manage and organize complex behavior within an application.
- Observer Pattern: Defines a one-to-many dependency between objects, so when one object changes state, all its dependents are notified and updated automatically.
- Strategy Pattern: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
- Command Pattern: Encapsulates a request as an object, allowing for parameterization of clients with queues, requests, and operations. Provides support for undoable operations.
- 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.
Understanding and implementing these patterns can significantly improve the flexibility, maintainability, and scalability of your code.