JavaData & JavaImplementing Behavioral Patterns in Java

Implementing Behavioral Patterns in Java

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories