Behavioral design patterns: what tasks they are needed for, types and examples of implementation

SHARE

In this part of the material about patterns, we’ll explore what behavioral design patterns are and what tasks they solve; on the top of that, we will also study the three most commonly used patterns.

Other articles of this series: «What are design patterns», Creational patterns», «Structural patterns».

 

ONE MORE TIME ABOUT PATTERNS

Design patterns are solutions to common application development problems. They are also known as object-oriented programming patterns. Unlike ready-made functions or libraries, a pattern is not a specific code, but a general concept for solving a problem which still needs to be adjusted to the tasks.

In total, there are 23 classic patterns that were described in the book by the “Gang of Four”. Depending on what tasks they solve, they are divided into creational, structural and behavioral. 

BEHAVIORAL PATTERNS

According to Wikipedia, behavioral patterns are design patterns that define algorithms and ways to implement the interaction of various objects and classes.

Simply put, behavioral patterns are related to the distribution of responsibilities between objects and describe the structure and patterns for passing messages / connections between components.

These include:

  • Template Method
  • Iterator
  • Observer
  • Chain of Responsibility
  • Command
  • Mediator
  • Memento
  • Visitor
  • Strategy
  • State

3 MOST POPULAR BEHAVIORAL PATTERNS

According to many developers, Template Method, Iterator and Observer are the most used behavioral patterns in development. Let’s see what tasks they help to cope with, and look at examples of their implementation.

Template Method 

According to Wikipedia, a Template Method is a behavioral design pattern that defines the basis of an algorithm and allows descendants to redefine some steps of the algorithm without changing its structure as a whole.

Simply put, a Template Method describes the skeleton of an algorithm shifting the responsibility for some of its steps to subclasses. The pattern allows subclasses to redefine the steps of an algorithm without changing its overall structure.

Even simpler: Builders use a template-like approach when building standardised homes. They have a basic architectural blueprint that outlines the construction steps: pouring the foundation, building walls, covering the roof, installing windows, and so on. But, despite the standardization of each stage, builders can make small changes at any of the stages to make the house a little different from the others.

When needed: used to avoid code duplication. For example, when describing templates.

The pattern consists of two parts:

  1. An abstract class that describes common properties and methods.
  2. A certain class that overrides the methods described in the abstract class or adds its own.

 

How to create:

  1. Create an abstract class.
  2. In it, create abstract methods (algorithm steps) and a final method that calls abstract methods (algorithm structure)
  3. Create an implementation of the abstract class and override the abstract methods.

 

Example of implementation:

Java:

abstract class AbstractHouse {
public final void build() {
pourFoundation();
putTheDoor();
buildRoof();
}
private void pourFoundation() { System.out.println("Foundation pouring"); }
abstract void putTheDoor();
abstract void buildRoof();
}
public class ConcreteHouse extends AbstractHouse {
@Override
void putTheDoor() { System.out.println("Wooden door installation"); }
@Override
void buildRoof() { System.out.println("Building a roof with a window"); }
}
JavaScript:
// Create an abstract class Car, assign a common property
// cost и method getCost
class Car {
constructor(cost) {
this.cost = cost;
}
getCost() {
return `Car cost: ${this.cost}`;
}
}
// create subclasses of a class Car that extend 
// its behaviour
class SportCar extends Car {
constructor(cost, speed) {
super(cost);
this.speed = speed;
}
getSpeed() {
return `Speed: ${this.speed}`
}
}
class Truck extends Car {
constructor(cost, capacity) {
super(cost);
this.capacity = capacity;
}
getCapacity() {
return `Capacity: ${this.capacity}`
}
}
const sport = new SportCar(16000, 300);
const truck = new Truck(20000, 1000);
// as we see, instance class SportCar
// has a method getSpeed, and insnatce Truck has a method
// getCapacity, but both instances have
// method of an abstract class Car
console.log(sport.getCost()); // Car price: 16000
console.log(sport.getSpeed()); // Speed: 300
console.log(truck.getCapacity()); // Capacity: 1000

Iterator

According to Wikipedia, Iterator is a behavioral design pattern; it represents an object that allows sequential access to the elements of an aggregate object without using descriptions of each of the aggregated objects.

Simply put, an Iterator is needed to sequentially iterate over the constituent elements of a collection without revealing their internal representation.

Even simpler: imagine going to a forest with many picturesque places. We want to visit specific ones, like the lake and the meadow of flowers, but it’s very easy to get lost in the forest. We have several options for how to get to the places: we can use a paper map or a navigator, try to find all the places ourselves, or ask the forester to guide us. In this example, the forest is a collection of scenic spots, and the phone, the map, the forester, and our brain are iterators over the “forest” collection.

When to use it: You should use an Iterator to iterate over complex collection objects if you want to hide implementation details from the client.

Pattern structure:

  1. Iterator describes an interface for accessing and traversing the elements of a collection.
  2. Specific iterator implements an algorithm for traversing a particular collection

 

How to create: 

  1. Create an interface with methods for moving through the elements of the collection: getting the next element, checking for the presence of the next element, moving to the first and last element
  2. Create an iterator interface implementation class for a specific collection type

 

Example of implementation: 
Java:

interface Iterator<T> {
T next();
void first();
void last();
boolean hasNext();
}
public class ListIterator<T> implements Iterator<T> {
private List<T> list;
private int index = 0;
public ListIterator(List<T> list) {
this.list = list;
}
public void first() { index = 0; }
public void last() { index = list.size(); }
public T next() {
T obj = list.get(index);
index++;
return obj;
}
public boolean hasNext() { return index < list.size(); }
}

JavaScript:

class Iterator {
constructor(data) {
this.index = 0;
this.data = data;
}
next() {
if(this.hasNext()) {
return {
value: this.data[this.index++],
done: false
}
}
return {
value: undefined,
done: true
}
}
hasNext() {
return this.index < this.data.length;
}
}
const cars = ['BMW', 'Audi', 'Honda'];
const carsIterator = new Iterator(cars);
console.log(carsIterator.next()); // {value: 'BMW', done: false}
console.log(carsIterator.next()); // {value: 'Audi', done: false}
console.log(carsIterator.hasNext()); // true
console.log(carsIterator.next()); // {value: 'Honda', done: false}
console.log(carsIterator.hasNext()); // false

Observer 

According to Wikipedia, Observer is a behavioral design pattern; it implements a class mechanism that allows an object of this class to receive notifications about changes in the state of other objects and thereby observe them.

Simply put, Observer allows one object to subscribe to other objects and track their changes.

Even simpler: after you subscribe to a newspaper or magazine, you no longer need to go to the supermarket and check if the next issue is out. The publisher will send new issues by mail directly to your home as soon as they are published.

When needed: Often used when designing libraries that manage application state. It is also the basis for various classes that can respond to changes and notify their subscribers about these changes: for example, NotificationCenter in iOS or LiveData in Android.

Pattern structure:

The pattern implies the presence of three participants:

  • subscriber, which is an interface/protocol that defines methods for sending notifications;
  • publisher which internal state is of interest to subscribers; it is the publisher that contains the subscription mechanism that includes a list of subscribers and methods for subscribing/unsubscribing;
  • concrete subscribers that implement the interface/protocol of the subscriber and carry specific business logic.

How to create:

  1. Create an observable object, or rather a subject which should provide an interface for registering and deregistering listeners
  2. Create one or more observers which must have a public method; through it, a notification will occur about a change in the state of the subject. This method is often referred to as notify.
  3. If there are a lot of observers, a collection of observers can be used to simplify work with them.

 

Example of implementation

Java: 

interface Observable {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
class ConcreteObservable implements Observable {
private List<Observer> parishioners = new ArrayList<>();
private String newsChurch;
public void setNewsChurch(String news) {
this.newsChurch = news;
notifyObservers();
}
public void registerObserver(Observer o) { parishioners.add(o); }
public void removeObserver(Observer o) { parishioners.remove(o); }
public void notifyObservers() { parishioners.forEach(o -> o.update(newsChurch)); }
}
interface Observer {
void update(String news);
}
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name, Observable o) {
this.name = name;
o.registerObserver(this);
}
public void update(String news) { System.out.println(name + " found out the news: " + news); }
}
JavaScript:
// create an Observer class which includes 3 methods
// subscribe to add subscribers
// unsubscribe to remove subscribers
// and fire to notify the subscribers about changes
class Observer {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
unsubscribe(fn) {
this.observers = this.observers.filter((subscriber, key) => subscriber !== fn);
}
fire(data) {
this.observers.forEach(subscriber => subscriber(data));
}
};
let clickCount = 0;
const clickObserver = new Observer();
const getMousePosition = (event) => {
console.log(`x:${event.clientX} y:${event.clientY}`);
};
const getCount = () => {
console.log(++clickCount);
// pay attention to this part of the code,
// here, we remove the subscription when the counter
// reaches 5, and the console will no longer display
// clickCount value
if(clickCount === 5) {
clickObserver.unsubscribe(getCount);
}
}
// here, we add a subscriber that will 
// output the mouse coordinates to the console 
// during a click
clickObserver.subscribe(getMousePosition);
// here, we add a subscriber that will
// increase the counter by 1 and output its result to the console
clickObserver.subscribe(getCount);
// next we add an event where the observer 
// will call all subscribers and pass them the
// event
document.addEventListener('click', (event) => {
clickObserver.fire(event);
});

CONCLUSION

Design patterns are solutions to common problems in code development. Knowing and using them allows you to save time by using ready-made solutions, standardize code and increase your common vocabulary.

Depending on what tasks design patterns solve, they are divided into three types: creational, structural and behavioral.

Behavioral patterns are related to the distribution of responsibilities between objects and describe the structure and patterns for passing messages / communications between components.

The three most popular ones are:

Template Method describes the skeleton of the algorithm shifting the responsibility for some of its steps to subclasses. The pattern allows subclasses to redefine the steps of an algorithm without changing its overall structure.

Iterator is needed to sequentially iterate over the constituent elements of a collection without revealing their internal representation. Useful for iterating over complex compound collection objects if you need to hide details from the client.

Observer allows one object to subscribe to other objects and track their changes. Often used when designing libraries that manage application state. It is also the basis for various classes that can respond to changes and notify their subscribers about these changes.

MATERIALS FOR ADDITIONAL STUDY 

Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, with a foreword by Grady Booch. — that very book by the “Gang of Four”.

Refactoring.guru — Design Patterns and Principles eBook