Behavioral Patterns are concerned with the strategy of object collaboration and the delegation of responsibilities among objects. This pattern characterizes object communication and simplifies the dynamic control flow of object behavior. Unlike the Creational and Structural patterns, which deal with the instantiation process and the blueprint of objects and classes, the central idea here is to concentrate on the way objects are interconnected. In a word, we can say this: If Creational is about instantiation, and Structural is the blueprint, then Behavioral is the pattern of the relationship among objects.
The Group
The Behavioral Pattern is the group of eleven design patterns, namely,
- Chain of Responsibility
- Command Pattern
- Interpreter Pattern
- Iterator Pattern
- Mediator Pattern
- Memento Pattern
- Observer Pattern
- State Patterns
- Strategy Patterns
- Template Method Patterns
- Visitor Patterns
Chain of Responsibility
This pattern promotes decoupling while percolating responsibility from one object to another, giving an equal chance to every receiving object to handle the request. Imagine in a office when a person calls, the first person takes the call. If the person is busy, the call is redirected to the second person and so on until no one is available. Then, an automated receiver replies to the caller. Here is an example of the chain of responders to handle the responsibility.
public class PhoneCall {
private int callId;
private String callerNumber;
private PhoneCallResponse call;
public PhoneCall(int callId, String callerNumber, PhoneCallResponse call) {
super();
this.callId = callId;
this.callerNumber = callerNumber;
this.call = call;
}
@Override
public String toString() {
return "PhoneCall [callId=" + callId + ", callerNumber=" + callerNumber
+ ", call=" + call + "]";
}
}
public enum PhoneCallResponse {
ACCEPTED,
REJECTED
}
public enum Status {
ONDESK,
OFFDESK
}
public abstract class CallHandlerBase {
protected CallHandlerBase redirectedTo;
protected Status status = Status.ONDESK;
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public void setRedirect(CallHandlerBase r) {
redirectedTo = r;
}
public abstract PhoneCallResponse response(PhoneCall call;
}
public class ReceptionHandler extends CallHandlerBase {
@Override
public PhoneCallResponse response(PhoneCall call) {
if (status == Status.ONDESK) {
System.out.println("Call:"+call.toString()+" received by the reception");
return PhoneCallResponse.ACCEPTED;
}
if (redirectedTo != null) {
return redirectedTo.response(call);
}
return PhoneCallResponse.REJECTED;
}
}
public class AdministrativeOfficeHandler extends CallHandlerBase {
@Override
public PhoneCallResponse response(PhoneCall call) {
if (status == Status.ONDESK) {
System.out.println("Call:" + call.toString()
+ " received by the Office Administration");
return PhoneCallResponse.ACCEPTED;
}
if (redirectedTo != null) {
return redirectedTo.response(call);
}
return PhoneCallResponse.REJECTED;
}
}
public class ManagerHandler extends CallHandlerBase {
@Override
public PhoneCallResponse response(PhoneCall call) {
if (status==Status.ONDESK) {
System.out.println("Call:"+call.toString()+" received by the Manager");
return PhoneCallResponse.ACCEPTED;
}
if (redirectedTo != null) {
return redirectedTo.response(call);
}
return PhoneCallResponse.REJECTED;
}
}
public class AutomatedSpeakerHandler extends CallHandlerBase {
@Override
public PhoneCallResponse response(PhoneCall call) {
System.out.println("Busy! "+call.toString()+" Please call later");
return PhoneCallResponse.ACCEPTED;
}
}
public class Test {
public static void main(String[] args) {
CallHandlerBase reception = new ReceptionHandler();
CallHandlerBase admin = new AdministrativeOfficeHandler();
CallHandlerBase manager = new ManagerHandler();
CallHandlerBase auto = new AutomatedSpeakerHandler();
reception.setRedirect(admin);
admin.setRedirect(manager);
manager.setRedirect(auto);
PhoneCall call1 = new PhoneCall(1, "9876543210",
PhoneCallResponse.ACCEPTED);
PhoneCall call2 = new PhoneCall(2, "9182736451",
PhoneCallResponse.ACCEPTED);
reception.setStatus(Status.ONDESK);
reception.response(call1);
reception.setStatus(Status.OFFDESK);
admin.setStatus(Status.OFFDESK);
manager.setStatus(Status.OFFDESK);
reception.response(call2);
}
}
Command Pattern
The Command Pattern encapsulates a command as an object so that it can be invoked when required. The command objects further can be queued or stacked as a batch of commands to revoke or to do an undo operation. The example describes the structure of this pattern, commonly found in any game. Although the Undo or revoking capabilities are not implemented here, they can be added easily to the structure.
public class Soldier {
public void moveForward(){
System.out.println("Moving forward...");
}
public void moveBackward(){
System.out.println("Moving backward...");
}
public void jump(){
System.out.println("Jumping...");
}
public void duck(){
System.out.println("ducking...");
}
public void shoot(){
System.out.println("shooting...");
}
}
public abstract class CommandBase {
protected Soldier soldier;
public CommandBase(Soldier soldier){
this.soldier=soldier;
}
public abstract void action();
}
public class DuckCommand extends CommandBase {
public DuckCommand(Soldier soldier) {
super(soldier);
}
@Override
public void action() {
soldier.duck();
}
}
public class JumpCommand extends CommandBase {
public JumpCommand(Soldier soldier) {
super(soldier);
}
@Override
public void action() {
soldier.jump();
}
}
public class MoveBackwardCommand extends CommandBase {
public MoveBackwardCommand(Soldier soldier) {
super(soldier);
}
@Override
public void action() {
soldier.moveBackward();
}
public class MoveForwardCommand extends CommandBase {
public MoveForwardCommand(Soldier soldier) {
super(soldier);
}
@Override
public void action() {
soldier.moveForward();
}
}
public class ShootCommand extends CommandBase {
publicShootCommand(Soldier soldier) {
super(soldier);
}
@Override
public void action() {
soldier.shoot();
}
}
public class Controller {
private HashMap<String, CommandBase> commands = new HashMap<>();
public Controller(HashMap<String, CommandBase> commands) {
super();
this.commands = commands;
}
public void executeAction(String commandString) {
if (!commands.containsKey(commandString))
System.out.println("Invalid Action!!!");
else
commands.get(commandString).action();
}
}
public class Test {
public static void main(String[] args) {
HashMap<String, CommandBase> sc1 = new HashMap<>();
Soldier s1 = new Soldier();
sc1.put("forward", new MoveForwardCommand(s1));
sc1.put("back", new MoveBackwardCommand(s1));
sc1.put("jump", new JumpCommand(s1));
sc1.put("duck", new DuckCommand(s1));
sc1.put("shoot", new ShootCommand(s1));
Controller control1 = new Controller(sc1);
control1.executeAction("duck");
control1.executeAction("jump");
control1.executeAction("forward");
control1.executeAction("shoot");
control1.executeAction("back");
control1.executeAction("adsgh");
}
}
Iterator Pattern
This pattern helps in accessing objects sequentially once they are aggregated as a list of objects. The real idea of this pattern is that it hides the underlying object representation. Thus, accessing objects, whether they are represented in a list or trees or any other structure, becomes possible in a common, standard way.
public interface Iterator { public int accelerate(int currentSpeed); public int deaccelerate(int currentSpeed); } public class Accelerator implements Iterator { @Override public int accelerate(int currentSpeed) { if (currentSpeed < 60) currentSpeed++; return currentSpeed; } @Override public int deaccelerate(int currentSpeed) { if (currentSpeed > 0) currentSpeed--; return currentSpeed; } } public class Controller { private int currentSpeed; private Accelerator accelerator>; public Controller(Accelerator accelerator){ this.accelerator=accelerator; this.currentSpeed=0; } public int paddleDown(){ currentSpeed=accelerator.accelerate(currentSpeed); return currentSpeed; } public int paddleUp(){ currentSpeed=accelerator.deaccelerate(currentSpeed); return currentSpeed; } } public class Test extends JFrame{ private static final long serialVersionUID = 1L; private Controller controller = new Controller(new Accelerator()); private JLabel label=new JLabel("0 mph"); private JButton accelerateButton=new JButton("Accelerate"); private JButton deaccelerateButton=new JButton("Deaccelerate"); public Test(){ accelerateButton.addActionListener(new ButtonHandler()); deaccelerateButton.addActionListener(new ButtonHandler()); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(400, 100); this.getContentPane().setLayout(new FlowLayout()); this.getContentPane().add(label); this.getContentPane().add(accelerateButton); this.getContentPane().add(deaccelerateButton); this.setVisible(true); } public static void main(String[] args) { new Test(); } public class ButtonHandler implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { if(e.getActionCommand()=="Accelerate"){ label.setText(controller.paddleDown()+ " mph"); }else{ label.setText(controller.paddleUp()+ " mph"); } } } }
Mediator Pattern
In the Mediator Pattern, when dissimilar classes want to communicate with one another, they do not do it directly. Rather, they work through a mediator class that, on behalf of both the parties, relays the message. This promotes loose coupling of objects by restraining objects to refer to each other explicitly. As a result, the sender and receiver objects can evolve and vary independently.
public class Aircraft { private AirTrafficControl controller; private int altitude; private String flightNumber; public Aircraft(String fno, AirTrafficControl a) { flightNumber = fno; controller = a; } public int getAltitude() { return altitude; } public void setAltitude(int altitude) { this.altitude = altitude; this.controller.sendMessage(this); } public String getFlightNumber() { return flightNumber; } public void setFlightNumber(String flightNumber) { this.flightNumber = flightNumber; } public void soar(int height) { this.altitude += height; } public void receiveMessage(Aircraft ra){ System.out.println(ra.flightNumber+" is at same altitude as yours!"); } } public interface AirTrafficControl { void registerFlight(Aircraft aircraft); void sendMessage(Aircraft aircraft); } public class AirTrafficControlImpl implements AirTrafficControl { private List<Aircraft> aircrafts = new ArrayList<>(); @Override public void registerFlight(Aircraft aircraft) { if (!aircrafts.contains(aircraft)) aircrafts.add(aircraft); } @Override public void sendMessage(Aircraft aircraft) { for (Aircraft a : aircrafts) { if (a != aircraft && (a.getAltitude() - aircraft.getAltitude()) < 1000) a.receiveMessage(aircraft); a.soar(1000); } } } public class Test { public static void main(String[] args) { AirTrafficControl controller = new AirTrafficControlImpl(); Aircraft a1 = new Aircraft("1234", controller); Aircraft a2 = new Aircraft("2345", controller); Aircraft a3 = new Aircraft("3456", controller); Aircraft a4 = new Aircraft("4567", controller); // a1.setAltitude(100); } }
Memento Pattern
The Memento Pattern helps to store the current state of objects without violating the rules of encapsulation. As a result, objects can be modified as per our purpose with the capability to restore them to their previous state at any point in time.
public class Book {
private String title;
private String author;
public Book(String title, String author) {
super();
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BookMemento createUndo(){
return new BookMemento(title, author);
}
public void undo(BookMemento bm){
title=bm.getTitle();
author=bm.getAuthor();
}
@Override
public String toString() {
return "Book [title=" + title + ", author=" + author + "]";
}
}
public class BookMemento {
private String title;
private String author;
public BookMemento(String title, String author) {
super();
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
public class BookCaretaker {
private Stack<BookMemento> mementos=new Stack<>();
public BookMemento getMemento(){
return mementos.pop();
}
public void setMemento(BookMemento bm){
mementos.push(bm);
}
}
public class Test {
public static void main(String[] args) {
Book b=new Book("Art of Programming Vol-I", "Donald Knuth");
BookCaretaker bc=new BookCaretaker();
bc.setMemento(b.createUndo());
System.out.println(b.toString());
b.setTitle("Art of Pogramming Vol II");
bc.setMemento(b.createUndo());
System.out.println(b.toString());
b.setAuthor("AAA");
System.out.println(b.toString());
b.undo(bc.getMemento());
System.out.println(b.toString());
b.undo(bc.getMemento());
System.out.println(b.toString());
}
}
Observer Pattern
The Observer Pattern helps create a dependency relationship among objects in such a way that when one object changes its state, the dependent objects are notified and updated automatically. It’s rather like a domino effect.
public abstract class Observer { protected Account account; public abstract void update(); } public class Account { private String accountNo; private List<Observer> observers = new ArrayList<>(); private float balance; public Account(String accno, float amount) { this.accountNo=accno; balance = amount; } public String getAccountNo() { return accountNo; } public float getBalance() { return balance; } public float withdraw(float amount) { float val=0.0f; if (balance < amount) val=0; else { balance = balance - amount; val=balance; } notifyAllObservers(); return val; } public void deposit(float amount) { balance += amount; notifyAllObservers(); } public void add(Observer o) { observers.add(o); } public void notifyAllObservers() { for (Observer o : observers) { o.update(); } } } public class EmailObserver extends Observer{ public EmailObserver(Account account) { this.account = account; this.account.add(this); } @Override public void update() { System.out.println("EMAIL: AccountNo:#" + this.account.getAccountNo() + " Balance: $" + this.account.getBalance()); } } public class SMSObserver extends Observer { public SMSObserver(Account account) { this.account = account; this.account.add(this); } @Override public void update() { System.out.println("SMS: AccountNo:#" + this.account.getAccountNo() + " Balance: $" + this.account.getBalance()); } } public class Test { public static void main(String[] args) { Account a=new Account("1122",5000.00f); new SMSObserver(a); new EmailObserver(a); a.deposit(200.0f); a.withdraw(300.0f); } }
State Pattern
In the State Pattern, objects’ behavior can be altered completely according to the change in the internal state of the object. The objects behave as if they have changed their class structures at run time.
public interface State {
public void doAction(Philosopher philosopher);
}
public class Philosopher {
private String name;
private State state;
public Philosopher(String name) {
super();
this.name = name;
}
//...getters and setters
}
public class implements State{
@Override
public void doAction(Philosopher philosopher) {
System.out.println(philosopher.getName()+" is in thinking state");
philosopher.setState(this);
}
}
public class EatingState implements State{
@Override
public void doAction(Philosopher philosopher) {
System.out.println(philosopher.getName()+" is in eating state");
philosopher.setState(this);
}
}
public class Test {
public static void main(String[] args) {
Philosopher p1 = new Philosopher("Socrates");
State thinkingState = new ThinkingState();
thinkingState.doAction(p1);
Philosopher p2 = new Philosopher("Aristotle");
State eatingState = new EatingState();
eatingState.doAction(p2);
eatingState.doAction(p1);
thinkingState.doAction(p2);
}
}
Strategy Pattern
The Strategy Pattern helps us encapsulate an algorithm into a class structure. Objects then can be instantiated dynamically according to the program’s requirement and suitability.
public class Soldier { private SoldierBehavior behavior; private String type; public Soldier(SoldierBehavior behavior, String type) { super(); this.behavior = behavior; this.type = type; } public void stance() { System.out.println(type); behavior.stance(); } } public interface SoldierBehavior { public void stance(); } public class AggresiveMode implements SoldierBehavior { @Override public void stance() { System.out.println("Attack enemy at sight."); } } public class DefensiveMode implements SoldierBehavior { @Override public void stance() { System.out.println("Attack only when attacked."); } } public class Test { public static void main(String[] args) { Soldier s1 = new Soldier(new DefensiveMode(), "Pikeman"; Soldier s2 = new Soldier(new AggresiveMode(), "Militia"); Soldier s3 = new Soldier(new DefensiveMode(), "Knight"); s1.stance(); s2.stance(); s3.stance(); } }
Template Method Pattern
This class behavioral pattern helps us structure a group of algorithms that produces a similar outcome but with different implementation details. These algorithms then can be further sub-classed and redefined without changing the original algorithm structure.
public abstract class SortingAlgorithm { public void sortingTemplate(int[] data){ electionSort(data); mergeSort(data); } public abstract void selectionSort(int[] data); public abstract void mergeSort(int[] data); } public class AlgorithmType extends SortingAlgorithm { @Override public void selectionSort(int[] data) { System.out.println("Selection sort algorithm Type 1"); } @Override public void mergeSort(int[] data) { System.out.println("Merge sort algorithm Type 1"); } } public class AlgorithmType2 extends SortingAlgorithm { @Override public void selectionSort(int[] data) { System.out.println("Selection sort algorithm Type 2"); } @Override public void mergeSort(int[] data) { System.out.println("Merge sort algorithm Type 2"); } } public class Test { public static void main(String[] args) { int[] data = { 45, 23, 89, 3423, 77, 33, 78, 322 }; SortingAlgorithm = new AlgorithmType1(); s1.sortingTemplate(data);; SortingAlgorithm s2 = new AlgorithmType2(); s2.sortingTemplate(data); } }
Visitor Pattern
The Visitor Pattern decouples the operation from the classes of the elements. The operation, however, is performed on the elements of the object structure. As a result, defining a new operation does not produce any ripples on the classes of elements on which it operates.
public interface PartsBase{ void accept(VisitorBase visitor); } public interface VisitorBase { void visit(CPU cpu); void visit(MotherBoard mb); void visit(RAM ram); } public class RAM implements PartsBase { private String specification; public String getSpecification() { return specification; } public void setSpecification(String specification) { this.specification = specification; } public RAM(String specification) { super(); this.specification = specification>; } @Override public void accept(VisitorBase visitor) { visitor.visit(this); } } public class MotherBoard implements PartsBase { private String specification; public String getSpecification() { return specification; } public void setSpecification(String specification) { this.specification = specification; } public MotherBoard(String specification) { super(); this.specification = specification; } @Override public void accept(VisitorBase visitor) { visitor.visit(this); } } public class CPU implements PartsBase { private String specification; public String getSpecification() { return specification; } public void setSpecification(String specification) { this.specification = specification; } public CPU(String specification) { super(); this.specification = specification; } @Override public void accept(VisitorBase visitor) { visitor.visit(this); } } public class Computer implements PartsBase { private List<PartsBase> parts = new ArrayList<>(); public Computer(List<PartsBase> parts) { super(); this.parts = parts; } @Override public void accept(VisitorBase visitor) { for (PartsBase p : parts) { p.accept(visitor); } } } public class ComputerPartsPrintVisitor implements VisitorBase { @Override public void visit(CPU cpu) { System.out.println(cpu.getSpecification()); } @Override public void visit(MotherBoard mb) { System.out.println(mb.getSpecification()); } @Override public void visit(RAM ram) { System.out.println(ram.getSpecification()); } } public class Test { public static void main(String[] args) { List<PartsBase> spec=new ArrayList<>(); spec.add(new CPU("Core 2 Duo")); spec.add(new MotherBoard("Intel GVSR")); spec.add(new RAM("4 GB")); VisitorBase vb=new ComputerPartsPrintVisitor(); Computer c=new Computer(spec); c.accept(vb); } }
Conclusion
In the Behavioral Pattern, the composition of objects is more important than inheritance. Different patterns describe how to collaborate peer objects to perform a task that is impossible for an isolated object. A well-designed systems works with the synergy of not only object composition but also pattern composition. In a way, these nomenclatures give a clue to its signature pattern and the motivation behind them.