Mastering Behavioral Design Patterns for Developers: Part 3
For Developers

February 15, 2024

Mastering Behavioral Design Patterns for Developers: Part 3

After , where creational and structural design patterns were explained, the third and final part is about behavioral design patterns.

Behavioral design patterns are the patterns that provide efficient communication and interaction between classes and objects and improve responsibilities between entities.

We will go through 10 design patterns:

  1. Observer
  2. Chain of responsibility
  3. Command
  4. Iterator
  5. Mediator
  6. Memento
  7. State
  8. Strategy
  9. Template method
  10. Visitor

Each pattern will be described by use-case from real-life and TypeScript code examples.

Also read: Mastering structural design patterns for developers: Part 2

1) Observer

Sometimes this pattern is also called Publish/Subscribe or Producer/Consumer but I will stick to the name Observer.

The observer is a design pattern that is used when you have too many relationships between one central object and many others.

This pattern provides an elegant way to manage situations when one object updates the state and needs to automatically notify and update other objects.

Usually, that central object is called a Subject, and dependent objects are Observers.

One of the best examples that embody this pattern is my neighbors Ane and Ina.

They are old ladies who like to observe everything from their windows. They see everything, and we don’t need security cameras in my building.

Let’s use them as an example for this pattern:

At the start, we have 2 interfaces NosyNeighbor and Person.

NosyNeighbor contains only 1 method, update. This method executes actions when the subject (me) notifies.

On the other hand, the interface Person contains 3 methods:

  • attach - used to attach the observer
  • detach - used to detach the observer
  • notify - used to notify the observer about the event

Following up, there are 3 classes:

  • Me - this class implements the Person interface with previously explained methods. There is also one more method livingMyLife which is setting hours to a random number
  • NeighborIna - implements the NosyNeighbor interface with the update method. Ina’s watch is up until 3 pm so she only observes up until that time.
  • NeighborAne - Same as NeighborIna but her watch is from 1 pm am to 3 pm

Now let’s test this code:

As you can see observers react to the state changes in the subject automatically.

2) Chain of responsibility

This pattern is good when you have specific request types which need to be handled by specific objects or handlers.

Each of these handlers can decide if they want to handle the request or pass it on to the next handler, and so on.

A good example would be handling articles if the store.

Each article must be handled on a specific shelf, you can’t place meat near the fruits, or sweets near the milk.

Here is the presentation of this use case in the code:

First, there is an interface Handler which consists of 2 functions:

  • setNext - used to pass the request onto the next available handler
  • handle - used for implementation of handling the request

Next, there is the abstract class ArticleHandler which implements the above 2 methods.

After that, there are 3 concrete handlers, FruitHandler, MeatHandler, and SweetsHandler. These handlers are very similar, the only difference is the preference for specific requests.

Now let’s test this code:

Function storeArticles is used to iterate over various types of articles and pass them to handlers.

As you can see, handlers either store the article on a shelf or pass it to the next handler.

After that, a chain of handlers is formed using setNext function.

Notice how if the first example when  fruitHandler is passed as a parameter, all items except milk are stored, as milk doesn’t have a handler.

In the second example, meatHandler is passed as a parameter and only meat and sweets articles are stored, as their handler are the only ones that are chained previously.

3) Command

This design pattern is often used when you have features with a lot of requests that can be executed in different ways and are all related to functionality.

With this pattern, you can avoid creating a huge number of classes and code repetition.

A good example of this design pattern is video games.

Usually, in video games, you have your character’s inventory which you can open in 2 or more ways:

  • Click the button on the in-game menu
  • Or press the keyboard shortcut, for example, the character “I”

Both of these commands are doing the same thing, they open the inventory menu in the game. So let’s implement this in the code:

At the begging there are 2 classes:

  • OpenInventoryKeyboardCommand - a class that accepts the game object in the constructor and implements the execute function, calling the openInventory function
  • OpenInventoryButtonCommand - same as OpenInventoryKeyboardCommand class

Next, 2 classes are the core of this pattern:

  • OperatingSystem - this is the invoker class that is used to store and execute all commands to the class with business logic. This class is not dependent on any specific commands, it just sends commands to the receiver class
  • VideoGame - this is a class that contains business logic, which means this class knows how to execute any command

Let’s see how it works:

As you can see commands are executed and the video game performs the action of opening the inventory.

Calling all software engineers seeking to work remotely! Join Index.dev remote-work platform and secure long-term full-time remote jobs with leading tech companies →

4) Iterator

When you have an iterable structure like a tree, stack, or list, you must traverse them to execute some operations.

To do this efficiently, but without exposing the internal logic or implementation, you can use the iterator design pattern.

A good example would be an address book or cake recipe book search.

When you search for a specific person in the address book or cake recipe, you need to go through the book to find it.

Everything is better with food, so let’s implement this cake recipe book example in the code:

First, there are 2 simple interfaces RecipeIterator and RecipeAggregator.

Next, the core of this pattern is 2 classes CakeRecipeIterator and CakeRecipes:

  • CakeRecipeIterator - a simple class that takes the recipes in the constructor and implements 2 methods, next and hasNext. The first one returns the next recipe and hasNext checks if there is the next element in the iterable collection.
  • CakeRecipes - this class is a wrapper around an iterable collection that implements the createIterator method and returns an iterator instance.

Now let’s see this in action:

As you can see all cake recipes are printed out with the help of an iterator.

5) Mediator

This pattern is a good choice when you have a big number of objects and their behavior is dependent on each other, so to reduce chaos in communication between them there is a mediator - a special object which is restricting direct communication between objects and forcing communication only through the mediator object.

One of the best examples of mediators in real life is police officers in situations where traffic lights are broken, so they have to guide the drivers and their cars and signal them when they can drive and when they must wait.

So let’s imagine there are 2 cars on opposite lanes and a patrol officer guiding the traffic:

The core of this pattern is PatrolOfficer class which implements the Mediator interface and notify method.

In that method, the mediator reacts only when the situation will cause a car crash, eg. car 1 needs to go left so car 2 needs to stop and wait, and vice-versa.

The next 2 classes are car objects with their functions driveLeft, driveStraight, and stopAndWait.

In each function, the mediator object is notified about the behavior and can react.

You can also see that when cars are driving straight mediator doesn’t intervene.

6) Memento

This design pattern is not used very often, but sometimes you need it in situations where you need to restore the previous state of an object without revealing any business logic in it.

A good example of a memento is the Maccy program I use every day.

Copy-paste is a very useful tool but when you copy something previously copied content is gone.

This is where Maccy comes in, it allows you to have a history of copied content and use it again. So it perfectly embodies the Memento design pattern with how it handles the clipboard state.

Let’s show it in the code:

First, there is ClipboardState class, which is very simple. It can hold one copy, has a getter and setter for that copied content, and that’s it.

Next is StateManager class, which also has a getter and setter for  ClipboardState instance, but also createMemento and setMemento functions. This class is the middleman between copied content and memento.

After that, there is the Memento class, which can store only one instance of ClipboardState and contains a getter for it.

Finally, MementoManager is a class that is a wrapper for the Memento object, with a getter and setter for it.

Below is the example section, you can see how copied content “This is another line I copied” can be restored to the clipboard state.

7) State

State lets you modify object behavior when its internal state changes, so it can behave like a completely different object.

If you are familiar with finite-state machines, this pattern is a very similar concept.

The coffee machine is a great example of a state design pattern:

  • when the coffee machine is turned on and ready to work
  • when is making a coffee
  • when the water tank is empty, it shows the symbol to fill it.

So basically, it has 3 different types of states. Of course, the coffee machine has more states but for the sake of simplicity, the code example can contain only these 3 above:

There is an interface State, followed by 3 classes that implement that interface:

  • CoffeeMachineReadyState - checks if the water tank is empty and if yes, it switches the machines state to WaterTankEmptyState, otherwise to MakingCoffeeState
  • WaterTankEmptyState displays the message that the water tank is empty
  • MakingCoffeeState which is displaying a message to make a coffee

Finally, there is CoffeeMachine class which contains a bunch of stuff, getter, and setter for a state, then functions to manage the water tank, handle state requests, and others.

Now let’s see how it works:

Notice how the machine switches states and when the water tank is empty until it’s refilled it won’t make coffee.

8) Strategy

This pattern is very popular and it lets you create a close group of strategies in separate classes and make their objects exchangeable.

A good example of strategy pattern application is open-world RPG games. They let you decide what you want to do next, so you have multiple choices:

  • You can roam free through the open world and do your own thing
  • You can do some side quests
  • You can do the main quest and progress the main story

So let’s implement this in the code:

First, there are 3 classes representing different strategies you can do in the game.

These classes can have business logic inside them, but in this example, they just print the message.

Next, there is a Context class, which has the property to store one strategy. Keep in mind, that Context is not responsible for choosing the strategy, it only delegates the work to the preferred one.

Finally, in the end, you can see how each strategy can be swapped for a new one and executed.

Calling all software engineers seeking to work remotely! Join Index.dev remote-work platform and secure long-term full-time remote jobs with leading tech companies →

9) Template method

This pattern is very useful when you have set of a specific instructions that can be used as a basis for different actions.

It allows you to define a core in the base class and then override the various steps in the subclasses without a change in the structure.

For example, you are making apple pie and pumpkin pie. The First 4 steps are identical:

  • Add flour
  • Add eggs
  • Add water
  • Mix to make a dough

The only difference is the main pie ingredient, which will be apples or pumpkin, so you need to add that and bake the pie to eat it.

Let’s implement this in the code:

The main class is the Pie class, which contains a template method called makeDough.

This method calls for all other repetitive steps that are used to make pie dough. It also contains two methods that must be implemented in the subclasses, these are addPieIngredient and bake.

Next, there are 2 classes ApplePie and PumpkinPie that implement previously explained methods.

Now to the testing part:

As you can see, the template method is executed so there is no need to repeat all these core steps in making pie dough, while specific steps such as adding apples or pumpkins are executed later.

10) Visitor

When you are in a situation where you need to separate business logic depending on the objects on which you operate, a visitor design pattern comes in handy.

Let’s take a singer for example. A singer will sing:

  • kid’s songs at kids’ birthdays,
  • his songs at his concert,
  • other songs or requested ones at weddings

So the singer is a visitor object.

Let’s try to implement this scenario in the code:

First, there are 2 interfaces IOcassion and ISinger to describe the above types of occasions and types of songs that singers can perform.

Then there are 3 classes and all of them implement accept method which calls the targeted function from the singer instance.

Each class also contains a specific method for that occasion with a song for it.

Finally, there is a Singer class that implements all methods from the ISinger interface and accepts the occasion as a parameter.

In the end, you can see the output with songs that the singer is singing.

That’s all for the final part, hope you enjoyed it and find it useful!

To all the senior developers yearning for remote software jobs with esteemed US and UK companies, your quest ends here. Index.dev opens doors to opportunities that resonate with your expertise. Index.dev engineers relish competitive salaries, exceeding market averages across countries. 

Register now and let the remote revolution redefine your career →