Behavioral Design Patterns


21 Sep 2021  Sergio Martin Rubio  14 mins read.

Behavioral patterns improve communication and flexibility between objects.

All Patterns

Observer

The observer pattern establishes a one to many relationship, so if an observable object is modified, all the observers listening to changes on that object get notified.

This pattern is used to reflect changes in an object when there are changes in another object, without having these two objects coupled. The observer pattern allows you to add additional observers in the future with minimal changes.

It’s important to highlight that if we have many observers and observables, the communication between them can become complex.

An Observable object needs to be created and then, the observers subscribe to the observable. Each time the observable state changes, attached observers are notified .

Implementation

Observer Pattern - UML
Observer Pattern - UML
  1. Create an observer interface:

     public interface Observer {
         void update();
     }
    
  2. Create a subject interface:

     public interface Subject {
         void register(Observer obj);
         void unregister(Observer obj);
         void notifyObservers();
     }
    
  3. Create a class implementing the Subject interface. This class will be responsible for registering, unregistering and notifying observers:

     public class MessageNotifier implements Subject, Runnable {
    
         private List<Observer> observers;
    
         private String message;
    
         public MessageNotifier() {
             observers = new ArrayList<>();
         }
    
         @Override
         public void register(Observer observer) {
             observers.add(observer);
         }
    
         @Override
         public void unregister(Observer observer) {
             observers.remove(observer);
         }
    
         @Override
         public void notifyObservers() {
             for (Observer observer : observers) {
                 observer.update();
             }
         }
    
         public String showMessage() {
             return this.message;
         }
    
         public void setMessage(String message) {
             this.message = message;
         }
    
         @Override
         public void run() {
             Scanner sc =  new Scanner(System.in);
             while (true) {
                 String response = sc.next();
                 setMessage(response);
                 notifyObservers();
             }
    
         }
     }
    
  4. Create concrete classes for observers implementing the Observer interface. The class implementing the subject interface is included by composition, so it has access to the updates:

     public class Mobile implements Observer {
    
         private MessageNotifier messageNotifier;
    
         public Mobile(MessageNotifier messageNotifier) {
             this.messageNotifier = messageNotifier;
         }
    
         @Override
         public void update() {
             System.out.println(messageNotifier.showMessage() + " from Mobile");
         }
     }
    
  5. The client is responsible for creating the subject class and registering the observers:

     System.out.println("Enter message: ");
     MessageNotifier messageNotifier =  new MessageNotifier();
     Mobile observerOne = new Mobile(messageNotifier);
     Desktop observerTwo = new Desktop(messageNotifier);
     Tablet observerThree = new Tablet(messageNotifier);
     messageNotifier.register(observerOne);
     messageNotifier.register(observerTwo);
     messageNotifier.register(observerThree);
        
     new Thread(messageNotifier).start();
    

Command

The command pattern is a data driven design pattern that allows you to create a set of behaviors for a particular subject. It decouples the object that invokes the action from the object that performs the action.

It is used to queue commands and keep a history of them to do and undo actions.

Implementation

  1. Create a command interface:

     public interface Command {
         void execute(UUID policyId);
     }
    
  2. Create a request class:

     public class Policy {
    
         public void create(UUID policyId) {
             System.out.println("Create policy " + policyId);
         }
    
         public void update(UUID policyId) {
             System.out.println("Update policy " + policyId);
         }
    
         public void cancel(UUID policyId) {
             System.out.println("Cancel policy " + policyId);
         }
     }
    
  3. Create concrete classes implementing the Command interface:

     public class CreatePolicy implements Command {
    
         private Policy policy;
    
         public CreatePolicy(Policy policy) {
             this.policy = policy;
         }
    
         @Override
         public void execute(UUID policyId) {
             policy.create(policyId);
         }
     }
    

  4. Create a command invoker class:

     public class PolicyCommandsInvoker {
    
         private List<Command> commands = new ArrayList<>();
    
         public void takeCommand(Command command) {
             commands.add(command);
         }
    
         public void runCommands(UUID policyId) {
             for (Command command : commands) {
                 command.execute(policyId);
             }
         }
     }
    
  5. The client can use the invoker class to execute commands:

     Policy policy = new Policy();
        
     CreatePolicy createPolicy = new CreatePolicy(policy);
     UpdatePolicy updatePolicy = new UpdatePolicy(policy);
     CancelPolicy cancelPolicy = new CancelPolicy(policy);
        
     PolicyCommandsInvoker policyCommandsInvoker = new PolicyCommandsInvoker();
     policyCommandsInvoker.takeCommand(createPolicy);
     policyCommandsInvoker.takeCommand(updatePolicy);
     policyCommandsInvoker.takeCommand(cancelPolicy);
        
     policyCommandsInvoker.runCommands(UUID.randomUUID());
    

Strategy

The strategy pattern defines a family of behaviors which are interchangeable. This pattern allows you to change at runtime the way an object behaves. Therefore, instead of having multiple instances, there will be only one that is able to interchange different behaviors, and the only requirement for these strategies or behaviors is having a common interface required by the context object.

Implementation

Strategy Pattern - UML
Strategy Pattern - UML
  1. Create strategy interface:

     public interface Operation {
         int doOperation(int number1, int number2);
     }
    
  2. Create concrete class implementing the strategy interface:

     public class Addition implements Operation {
         @Override
         public int doOperation(int number1, int number2) {
             return number1 + number2;
         }
     }
    

  3. Create a context class that accepts :

     public class CalculatorContext {
    
         private final Operation operation;
    
         public CalculatorContext(Operation operation) {
             this.operation = operation;
         }
    
         public int executeStrategy(int num1, int num2) {
             return operation.doOperation(num1, num2);
         }
     }
    
  4. The client will use the context class to execute strategies:

     System.out.println("Do Addition Operation...");
     CalculatorContext calculator = new CalculatorContext(new Addition());
     System.out.println(NUM1 + " + " + NUM2 + ": " + calculator.executeStrategy(NUM1, NUM2));
        
     System.out.println("Do Substration Operation...");
     calculator = new CalculatorContext(new Subtraction());
     System.out.println(NUM1 + " - " + NUM2 + ": " + calculator.executeStrategy(NUM1, NUM2));
        
     System.out.println("Do Division Operation...");
     calculator = new CalculatorContext(new Division());
     System.out.println(NUM1 + " / " + NUM2 + ": " + calculator.executeStrategy(NUM1, NUM2));
        
     System.out.println("Do Multiplication Operation...");
     calculator = new CalculatorContext(new Multiplication());
     System.out.println(NUM1 + " * " + NUM2 + ": " + calculator.executeStrategy(NUM1, NUM2));
    

Visitor

The visitor pattern allows you to define operations on existing objects without having to change the structure of the class.

One of the most common use cases of this pattern is when the same interface is used by multiple classes and adding additional operations to the target class will require changing the interface definition, and as a result we will have the other classes implementing the interface accordingly.

Implementation

classDiagram

class Client {
	
}

class Visitor {
	<<interface>>
	visit(Computer)
	visit(DishWasher)
}

class RewardPointsVisitor {
  -double rewardPoints
  +visit(Computer)
  +visit(DishWasher)
  +getRewardPoints()
}

class Visitable {
	<<interface>>
	accept(Visitor)
}

class Computer {
  -double price
  +accept(Visitor)
  +getPrice()
}

class DishWasher {
  -double price
  +accept(Visitor)
  +getPrice()
}

Visitable <|-- Computer : implements
Visitable <|-- DishWasher : implements
Client ..> Visitable

Visitor <|-- RewardPointsVisitor : implements
Client ..> Visitor



  1. Assuming we have a hierarchy of classes like Computer and DishWasher that implement a common interface Visitable that has an operation called accept.

    public interface Visitable {
        void accept(Visitor visitor);
    }
    
    public class Computer implements Visitable {
        private final double price;
       
        public Computer(double price) {
            this.price = price;
        }
       
        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
       
        public double getPrice() {
            return price;
        }
       
    }
    
  2. We create a Visitor that declares a visit method for each concrete visitor class. In the previous diagram we can see we have two visit operations, one for the Computer class and another one for the DishWasher class. The signature of the visit method contains the classe to be visited, and this lets the visitor determine the concrete class to be visited, so the concrete visitor will be able to access the visitable class directly.

    public interface Visitor {
        void visit(DishWasher dishWasher);
       
        void visit(Computer computer);
    }
    
  3. We create concrete visitor classes like RewardPointsVisitor that implements each method declared by the Visitor. The concrete class allows you to define new operations of the visitable classes and stores its local state.

    public class RewardPointsVisitor implements Visitor {
        private double rewardPoints; // stores local state
       
        @Override
        public void visit(DishWasher dishWasher) {
            if (dishWasher.getPrice() > 500) {
                rewardPoints += dishWasher.getPrice() * 2;
            } else {
                rewardPoints += dishWasher.getPrice();
            }
        }
       
        @Override
        public void visit(Computer computer) {
            if (computer.getPrice() > 1000) {
                rewardPoints += computer.getPrice() * 3;
            } else {
                rewardPoints += computer.getPrice();
            }
        }
       
        public double getRewardPoints() {
            return rewardPoints;
        }
    }
    
  4. The client can now iterate through the hierarchy and apply the visitors.

    public class Client {
        public static void main(String[] args) {
            List<Visitable> products = new ArrayList<>();
            products.add(new Computer(600));
            products.add(new DishWasher(600));
            products.add(new Computer(1200));
            products.add(new Computer(300));
       
            RewardPointsVisitor rewardPointsVisitor = new RewardPointsVisitor();
       
            products.forEach(product -> product.accept(rewardPointsVisitor));
       
            System.out.println(rewardPointsVisitor.getRewardPoints());
        }
    }
    

As you can see the visitor pattern allows you to add operations to classes without chaing them. This is achieved by using a technique called double-dispatch. This technique consist of providing two types of receivers, in this case the concrete Visitor and the concrete visitable class.

When applying this pattern it is important to remember that each visitor has to visit each concrete class of the object hierarchy.

Image by Pexels from Pixabay