Top Behavioral Design Patterns With Real Examples In Java

Amirhosein Gharaati
Javarevisited
Published in
12 min readOct 2, 2023

We can apply solutions to commonly occurring problems by knowing design patterns in software design.

Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects.

Table of Contents

If you have already read “Creational Patterns” or “Structural Patterns”, jump directly to Behavioral Patterns section.

Design patterns

Design patterns are solutions to commonly occurring problems in software design. They are like blueprints that you can customize to solve a recurring design problem in your code.

The pattern is not a specific piece of code, but a general concept that you can apply it in your software or customize them and use your own.

Why should we learn design patterns?

There are couple reasons to learn design patterns:

  1. They are tried and tested solutions: and even if you don’t encounter with those problems, knowing patterns teach you how to solve them in object oriented design.
  2. They define a common language that you and your teammates can use to communicate with each other more efficiently. When you say “Factory”, maybe everyone understands the idea that you are talking about.

What does the pattern consist of?

The sections that are usually present in a pattern description are:

  • Intent: describes both the problem and the solution.
  • Motivation: further explains the problem and the solution the pattern makes possible.
  • Structure: shows each part of the pattern and how they are related.
  • Code example: in a programming language to show the solution.

But for simplicity, we summarize these parts.

Classification of Patterns

There are three main groups of design patterns in software development:

  • Creational patterns: provide object creation mechanisms that increase flexibility and reuse of existing code.
  • Structural patterns: explain how to assemble objects and classes into larger structures, while keeping these structures flexible and efficient.
  • Behavioral patterns: take care of effective communication and the assignment of responsibilities between objects.

In this article, we only talk about popular structural patterns.

Behavioral Patterns

So in behavioral design we are trying to deal with algorithms and the assignment of responsibilities between objects.

You can see the implementations in this repository:

https://github.com/AmirHosein-Gharaati/design-patterns

Let’s see some popular patterns with examples.

Strategy

Lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

A real general example is about sorting. Sometimes we care about what kind of sorting we need, but we know that we want to sort. Just the algorithm is different. But let’s go through another example.

I am not an AI engineer, but imagine you have a recommendation system in a retail system. You have different strategies for your recommendation system. Based on the user experience, you may use a special algorithm to recommend some products.

In this pattern we create an interface and force the classes to implement a method:
RecommendationStrategy

public interface RecommendationStrategy {
List<String> getRecommendations(String userId);
}

Now we can define our algorithms and implement the interface.

CollaborativeFilteringStrategy

@NoArgsConstructor
public class CollaborativeFilteringStrategy implements RecommendationStrategy {

@Override
public List<String> getRecommendations(String userId) {
// Recommend products based on the purchase history of similar users
return List.of();
}
}

ContentBasedFilteringStratgey

@NoArgsConstructor
public class ContentBasedFilteringStrategy implements RecommendationStrategy {

@Override
public List<String> getRecommendations(String userId) {
// Recommend products based on user preferences and product tags
return List.of();
}
}

Since we have these algorithms, within our system we can use each of these to recommend based on the strategy we decide.

RetailSystem

@Setter
@AllArgsConstructor
public class RetailSystem {
private RecommendationStrategy recommendationStrategy;

public List<String> getRecommendations(String userId) {
if(recommendationStrategy == null) {
return List.of();
}
return recommendationStrategy.getRecommendations(userId);
}
}

Let’s see the usage:

Main

public class Main {
public static void main(String[] args) {
RecommendationStrategy strategy1 =
new CollaborativeFilteringStrategy();

RetailSystem retailSystem = new RetailSystem(strategy1);

String userId = "123";
List<String> recommendedProducts =
retailSystem.getRecommendations(userId);

retailSystem.setRecommendationStrategy(
new ContentBasedFilteringStrategy()
);
List<String> recommendedProducts2 =
retailSystem.getRecommendations(userId);
}
}

The returned list is empty since we didn’t implement any algorithm.

Applicability

Use strategy pattern when:

  • You want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime.
  • You have a lot of similar classes that only differ in the way they execute some behavior.
  • You want to isolate the business logic of a class from the implementation details of algorithms.

Pros

  • You can swap algorithms used inside an object at runtime.
  • You can replace inheritance with composition.
  • You can introduce new strategies without having to change the context. (Open/Closed Principle)

Cons

  • If you only have a couple of algorithms and they rarely change, there’s no real reason to over-complicate the program with new classes and interfaces that come along with the pattern.
  • Clients must be aware of the differences between strategies to be able to select a proper one.

Read more about strategy pattern:

Mediator

Lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.

So you want to create a multi-module system where clients can purchase and interact with various modules.

Each module needs to communicate with others through a mediator to check if specific modules are present or not and take appropriate actions based on their availability.

Let’s see the mediator in action.

We have different modules, each of them have a name

MyModule

public interface MyModule {
String getName();
}

We define our modules to implement the interface:

InventoryModule

public class InventoryModule implements MyModule {
@Override
public String getName() {
return "INVENTORY";
}
}

PurchaseModule

public class PurchaseModule implements MyModule {
@Override
public String getName() {
return "PURCHASE";
}
}

Now we define our mediator:

ModuleMediator

public interface ModuleMediator {
void registerModule(MyModule module);
boolean isModuleAvailable(String moduleName);
void moduleAction(String moduleName);
}

Mediator

public class Mediator implements ModuleMediator {
private List<MyModule> modules;

public Mediator() {
this.modules = new ArrayList<>();
}

@Override
public void registerModule(MyModule module) {
this.modules.add(module);
}

@Override
public boolean isModuleAvailable(String moduleName) {
return modules.stream()
.anyMatch(module -> module.getName().equals(moduleName));
}

@Override
public void moduleAction(String moduleName) {
if (isModuleAvailable(moduleName)) {
System.out.println("Performing action for module: " + moduleName);
} else {
System.out.println("Module not available: " + moduleName);
// Take appropriate action when the module is not available
}
}
}

Let’s see the usage:

Main

public class Main {
public static void main(String[] args) {
ModuleMediator mediator = new Mediator();

mediator.registerModule(new PurchaseModule());
mediator.registerModule(new InventoryModule());

mediator.moduleAction("PURCHASE");
mediator.moduleAction("INVENTORY");
mediator.moduleAction("REPORT");
}
}

We did not define a module with the name “REPORT”.
So we expect the program says: it is not available.

Output:

Performing action for module: PURCHASE
Performing action for module: INVENTORY
Module not available: REPORT

Applicability

Use mediator pattern when:

  • It’s hard to change some of the classes because they are tightly coupled to a bunch of other classes.
  • You can’t reuse a component in a different program because it’s too dependent on other components.
  • You find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts.
  • (Personal Opinion) You have cyclic dependency and you want to resolve it. Actually we shouldn’t have such thing. Maybe there is a problem in the design.

Pros

  • You can extract the communications between various components into a single place (Single Responsibility principle).
  • You can introduce new mediators without having to change the actual components. (Open/Closed principle)
  • You can reduce coupling between various components of a program.

Cons

  • Over time a mediator can evolve into a God Object.

Read more about mediator pattern:

Observer

Lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

This pattern is popular in reactive programming. There are many use cases for this pattern in software development: Stock market updates, Weather updates, Notifications, Distributed event handling, and . . .

Also in front-end development, this pattern is popular when we want to react to the behavior of a user. For example, disabling a text field when a tick triggers in a form.

One of the examples I see on the medium website is when we want to publish a post, it says you can notify and send an email to followers to see your post.

Let’s implement this in Java. We don’t want to use a database or something like that.

We have two main concepts: Observer and Subject.

The observer listens to the updates that happen in Subject. (Observer is a subscriber)

In medium, we have some followers. Let’s consider them as Observer:

FollowerObserver

public interface FollowerObserver {
void sendEmail(String postId);
}

UserFollower

@AllArgsConstructor
public class UserFollower implements FollowerObserver {
private final String username;

@Override
public void sendEmail(String postId) {
System.out.println(username + " received an email about new post with id: " + postId);
}
}

Now we create our subject about posts:
PostSubject

public interface PostSubject {
void addObserver(FollowerObserver follower);
void removeObserver(FollowerObserver follower);
void notifyFollowers(String postId);
}

Implementing the subject:

MeidumUser

public class MediumUser implements PostSubject {
private final List<FollowerObserver> followers;

public MediumUser() {
this.followers = new ArrayList<>();
}

@Override
public void addObserver(FollowerObserver follower) {
followers.add(follower);
}

@Override
public void removeObserver(FollowerObserver follower) {
followers.remove(follower);
}

@Override
public void notifyFollowers(String postId) {
followers.forEach(follower -> follower.sendEmail(postId));
}
}

Assume you have some followers in your profile. When you post, all of the followers should be notified about your post update. So you notify them about the new post id:

Main

public class Main {
public static void main(String[] args) {
MediumUser user = new MediumUser();

FollowerObserver followerObserver1 = new UserFollower("followerObserver1");
FollowerObserver followerObserver2 = new UserFollower("followerObserver2");
FollowerObserver followerObserver3 = new UserFollower("followerObserver3");

user.addObserver(followerObserver1);
user.addObserver(followerObserver2);
user.addObserver(followerObserver3);

String postId = "123";
user.notifyFollowers(postId);

user.removeObserver(followerObserver2);

String newPostId = "456";
user.notifyFollowers(newPostId);
}
}

Output:

followerObserver1 received an email about new post with id: 123
followerObserver2 received an email about new post with id: 123
followerObserver3 received an email about new post with id: 123
followerObserver1 received an email about new post with id: 456
followerObserver3 received an email about new post with id: 456

Applicability

Use observer pattern when:

  • Changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically.
  • Some objects in your app must observe others, but only for a limited time or in specific cases.

Pros

  • You can introduce new subscriber classes without having to change the publisher’s code. (Open/Closed Principle).
  • You can establish relations between objects at runtime.

Cons

  • Maybe subscribers are notified in random order.

Read more about observer pattern:

More Behavioral Patterns

Chain of Responsibility

Lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

One of the examples is in the Spring Boot filter chain. There is a chain of different filters we can register. When the first filter is resolved, the request goes to the next filter, and so on.

This pattern is very good when we need to add or remove chains easily. Also, you can set the order for the methods and chains to do one by one.

Here is a god example of Chain of Responsibility pattern in Java:

Command

Turns a request into a stand-alone object that contains all information about the request and lets you pass requests as a method arguments, delay or queue a request’s execution.

So in this pattern, we are going to act different behaviors for an object. Let’s say you are developing a GUI interface. There are different actions to do. Copy, paste, undo, redo and …

From the user interface, you want to send some requests to the logic layer. The logic layer doesn’t know and care about the user interface objects. Maybe it only knows about a command that should be executed.

So the things that come from the user interface, translate into a command interface which has an “execute” method and the process goes on.

Here is another good example of command pattern in Java:

Iterator

Lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).

This is a well-known pattern that we are working with. We iterate over something that we don’t know about the data structure and the complexity behind it.

A use case can be when we have complex data structure(s), and we want to hide the implementation and complexity of traversing the elements.

You can the implementation of a custom iterator in Java: https://stackoverflow.com/questions/5849154/can-we-write-our-own-iterator-in-java

Memento

Lets you save and restore the previous state of an object without revealing the details of its implementation.

This pattern is used to capture and externalize the internal state of an object so that the object can be restored to that state later.

It is useful when you need to implement undo/redo functionality or need to save and restore the state of an object without exposing its internal details. So you can produce snapshots of the object’s state to be able to restore a previous state of the object.

State

Lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.

There shall be a separate concrete class per possible state of an object. Each concrete state object will have logic to accept or reject a state transition request based on its present state.

In any application, when we are dealing with an object that can be in different states during its life cycle and how it processes incoming requests based on its present state — we can use the state pattern.

If we do not use the state pattern in such cases, we will end up having lots of ”if-else” statements which make the code base ugly, unnecessarily complex, and hard to maintain.

See one good example in this article:

Template Method

Defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

So this pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It allows subclasses to redefine certain steps of the algorithm without changing its structure.

Imagine you have a data processor in your system. There are different steps the process data:
“initialize”, “read data”, “transform data”, “save data” and “cleanup”

You may have implemented some default behaviors for each step. But let’s say you have different data sources. You have to implement the “initialize” step or “read data” step for each and leave other steps to do their operations with default behavior.

Visitor

Lets you separate algorithms from the objects on which they operate.

This pattern is trying to separate the algorithm from the object structure on which it operates. It allows you to add new operations or behaviors to a class hierarchy without modifying the existing classes.

Let’s consider a simplified scenario where you have a shopping cart containing different types of products, and you want to apply different types of discounts to the products in the cart. The Visitor Pattern can help you achieve this by allowing you to define discount calculation logic in separate visitor classes.

Conclusion

There are different groups of design patterns in software design which we talked about “Behavioral patterns”.

Choosing the proper pattern, can make our project more flexible and reusable. We should only get the idea behind a pattern, and apply it to our software.

--

--