Behavioral Patterns and it’s types in java
Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. These patterns are designed to improve or simplify the communication between different objects in a system.
Mediator Method Design Pattern Memento Method Design Patterns Observer Method Design Pattern State Method Design Pattern Strategy Method Design Pattern Template Method Design Pattern Visitor Method Design Pattern
(Share all of examples with advantage and Disadvantages as shared above for Behavioral Patterns and it’s types in java design pattern)
Table of Contents
Types of Behavioral Design Patterns
- 1. Chain of Responsibility Pattern
- 2. Command Pattern
- 3. Interpreter Pattern
- 4. Mediator Pattern
- 5. Memento Pattern
- 6. Observer Pattern
- 7. State Pattern
- 8. Strategy Pattern
- 9. Template Method Pattern
- 10. Visitor Pattern
Chain of Responsibility Pattern
The Chain of Responsibility pattern creates a chain of receiver objects for a request. This pattern decouples the sender and receiver by allowing multiple objects to handle the request without explicitly coupling the sender class to the receiver classes Behavioral Patterns.
Advantages
- Reduces the coupling between the sender and receiver of a request.
- Adds flexibility in assigning responsibilities to objects.
- Simplifies the code by avoiding a lot of `if-else` or `switch-case` statements.
Disadvantages
- Some requests may not be handled at all if the chain is not properly configured.
- Debugging can be difficult due to the dynamic nature of the chain.
Example
```java
abstract class Logger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected Logger nextLogger;
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
class ConsoleLogger extends Logger {
public ConsoleLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
class ErrorLogger extends Logger {
public ErrorLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("Error Console::Logger: " + message);
}
}
class FileLogger extends Logger {
public FileLogger(int level) {
this.level = level;
}
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
public class ChainPatternDemo {
private static Logger getChainOfLoggers() {
Logger errorLogger = new ErrorLogger(Logger.ERROR);
Logger fileLogger = new FileLogger(Logger.DEBUG);
Logger consoleLogger = new ConsoleLogger(Logger.INFO);
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
Logger loggerChain = getChainOfLoggers();
loggerChain.logMessage(Logger.INFO, "This is an information.");
loggerChain.logMessage(Logger.DEBUG, "This is a debug level information.");
loggerChain.logMessage(Logger.ERROR, "This is an error information.");
}
}
```
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 Behavioral Patterns for undoable operations.
Advantages
- Decouples the classes that invoke the operation from the object that performs the operation.
- Enables the implementation of a command history or undo mechanism.
Disadvantages
Can result in a system with many small classes.
Example
```java
interface Command {
void execute();
}
class Light {
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOn();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.turnOff();
}
}
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
public class CommandPatternDemo {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();
remote.setCommand(lightOff);
remote.pressButton();
}
}
```
Interpreter Pattern
The Interpreter pattern defines a grammatical representation for a language and an interpreter to deal with this grammar. It is used for parsing Behavioral Patterns expressions defined in that language.
Advantages
- Easy to change and extend the grammar.
- Adding new expressions is relatively simple.
Disadvantages
- Complex grammars are hard to maintain.
- Not optimal for languages with a lot of grammar rules.
```java
interface Expression {
boolean interpret(String context);
}
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data) {
this.data = data;
}
@Override
public boolean interpret(String context) {
return context.contains(data);
}
}
class OrExpression implements Expression {
private Expression expr1;
private Expression expr2;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
class AndExpression implements Expression {
private Expression expr1;
private Expression expr2;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
public class InterpreterPatternDemo {
public static void main(String[] args) {
Expression isJava = new TerminalExpression("Java");
Expression isPython = new TerminalExpression("Python");
Expression isJavaOrPython = new OrExpression(isJava, isPython);
System.out.println("Does the context 'Java' contain 'Java' or 'Python'? " + isJavaOrPython.interpret("Java"));
System.out.println("Does the context 'Python' contain 'Java' or 'Python'? " + isJavaOrPython.interpret("Python"));
System.out.println("Does the context 'C++' contain 'Java' or 'Python'? " + isJavaOrPython.interpret("C++"));
}
}
```
Mediator Pattern
The Mediator pattern provides a way to encapsulate how objects interact, promoting loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
Advantages
- Reduces the dependencies between communicating objects.
- Centralizes control logic for complex communication and interactions.
Disadvantages
- Can result in a single point of failure.
- Mediator can become very large and complex.
```java
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
class ConcreteMediator implements Mediator {
private Colleague1 colleague1;
private Colleague2 colleague2;
public void setColleague1(Colleague1 colleague1) {
this.colleague1 = colleague1;
}
public void setColleague2(Colleague2 colleague2) {
this.colleague2 = colleague2;
}
@Override
public void sendMessage(String message, Colleague colleague) {
if (colleague == colleague1) {
colleague2.receiveMessage(message);
} else if (colleague == colleague2) {
colleague1.receiveMessage(message);
}
}
}
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
}
class Colleague1 extends Colleague {
public Colleague1(Mediator mediator) {
super(mediator);
}
public void sendMessage(String message) {
System.out.println("Colleague1 sends message: " + message);
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println("Colleague1 received message: " + message);
}
}
class Colleague2 extends Colleague {
public Colleague2(Mediator mediator) {
super(mediator);
}
public void sendMessage(String message) {
System.out.println("Colleague2 sends message: " + message);
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println("Colleague2 received message: " + message);
}
}
public class MediatorPatternDemo {
public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
Colleague1 colleague1 = new Colleague1(mediator);
Colleague2 colleague2 = new Colleague2(mediator);
mediator.setColleague1(colleague1);
mediator.setColleague2(colleague2);
colleague1.sendMessage("Hello from Colleague1!");
colleague2.sendMessage("Hello from Colleague2!");
}
}
```
Memento Pattern
The Memento pattern provides the ability to restore an object to its previous state (undo via rollback).
Advantages
- Provides the ability to restore an object to its previous state.
- Ensures encapsulation by not exposing the internal state details to the caretaker.
Disadvantages
Can be costly in terms of memory if the state history is large.
Example
```java
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento saveStateToMemento() {
return new Memento(state);
}
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento state) {
mementoList.add(state);
}
public Memento get(int index) {
return mementoList.get(index);
}
}
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State #1");
originator.setState("State #2");
caretaker.add(originator.saveStateToMemento());
originator.setState("State #3");
caretaker.add(originator.saveStateToMemento());
originator.setState("State #4");
System.out.println("Current State: " + originator.getState());
originator.getStateFromMemento(caretaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(caretaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
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.
Advantages
- Establishes a relationship between objects without making them tightly coupled.
- Supports broadcast communication.
Disadvantages
- Notification to all subscribers could be a performance bottleneck if there are many observers.
- Difficult to ensure the state consistency across observers.
Example
```java
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
subject.attach(observer1);
subject.attach(observer2);
subject.notifyObservers("Hello Observers!");
subject.detach(observer1);
subject.notifyObservers("Goodbye Observers!");
}
}
```
State Pattern
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Advantages
- Localizes state-specific behavior and partitions behavior for different states.
- Makes state transitions explicit and clear.
Disadvantages
Can result in a lot of subclasses and redundant code if not implemented carefully.
Example
```java
interface State {
void doAction(Context context);
}
class StartState implements State {
public void doAction(Context context) {
System.out.println("Player is in start state");
context.setState(this);
}
public String toString() {
return "Start State";
}
}
class StopState implements State {
public void doAction(Context context) {
System.out.println("Player is in stop state");
context.setState(this);
}
public String toString() {
return "Stop State";
}
}
class Context {
private State state;
public Context() {
state = null;
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
}
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}
```
Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
Advantages
- Provides a way to configure a class with one of many behaviors.
- Reduces conditional statements in the client.
Disadvantages
- Clients must be aware of different strategies.
- Increased number of objects or classes.
```java
interface Strategy {
int doOperation(int num1, int num2);
}
class OperationAdd implements Strategy {
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
class OperationSubtract implements Strategy {
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
class OperationMultiply implements Strategy {
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
Template Method Pattern
The Template Method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It allows subclasses to redefine certain steps of an algorithm without changing its structure.
Advantages
- Promotes code reuse by defining the invariant parts of an algorithm.
- Encourages the use of a common algorithm in a consistent manner.
Disadvantages
- Increases the complexity by requiring a subclass to change algorithm behavior.
- Can lead to code duplication in subclasses.
```java
abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
// Template method
public final void play() {
initialize();
startPlay();
endPlay();
}
}
class Cricket extends Game {
@Override
void initialize() {
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started. Enjoy the game!");
}
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
}
class Football extends Game {
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}
```
Visitor Pattern
The Visitor pattern represents an operation to be performed on elements of an object structure. It lets you define a new operation without changing the classes of the elements on which it operates.
Advantages
- Simplifies the addition of operations.
- Promotes the separation of concerns by moving unrelated behavior out of the elements themselves.
Disadvantages
- Can make the code more complex and difficult to maintain.
- Requires knowing the internals of the classes being visited.
Example
```java
interface ComputerPart {
void accept(ComputerPartVisitor computerPartVisitor);
}
class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
class Computer implements ComputerPart {
ComputerPart[] parts;
public
Computer() {
parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
interface ComputerPartVisitor {
void visit(Computer computer);
void visit(Mouse mouse);
void visit(Keyboard keyboard);
void visit(Monitor monitor);
}
class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
```
These examples cover various behavioral patterns in Java, showcasing their advantages and disadvantages through practical code snippets. Each pattern provides a unique approach to solving common design problems, enhancing the flexibility and maintainability of the code.