codingstuff.io
ExploreTutorialsProblemsCS Subjects
Get Started
ExploreTutorialsProblemsCS Subjects
Get Started
codingstuff.io

Master the art of building software through interactive tutorials, real-world problems, and guided projects.

Pune, Maharashtra, India

codingstuffmail@gmail.com

Product

  • Explore
  • Tutorials
  • Problems
  • CS Subjects

Company

  • About
  • Contact
  • Privacy Policy
  • Terms & Conditions
  • Sitemap

© 2026 codingstuff.io. All rights reserved.

Built with ❤️ for developers everywhere

/
/
All Tutorials
🎭

Design Patterns

33 / 100 topics
19Introduction to Behavioral Patterns20Chain of Responsibility Pattern21Command Pattern22Interpreter Pattern23Iterator Pattern24Mediator Pattern25Memento Pattern26Observer Pattern27State Pattern28Strategy Pattern29Template Method Pattern30Visitor Pattern33Practical Exercises for Behavioral Patterns
Tutorials/Design Patterns/Practical Exercises for Behavioral Patterns
🎭Design Patterns

Practical Exercises for Behavioral Patterns

Updated 2026-05-15
10 min read

Practical Exercises for Behavioral Patterns

Introduction

Behavioral design patterns are a category of design patterns that identify common solutions to software design problems related to communication between objects. These patterns focus on the interaction and responsibilities between objects, rather than their structure or implementation. In this tutorial, we will explore several behavioral patterns through practical exercises that you can apply in real-world scenarios.

Concept

Behavioral patterns are classified into three main categories:

  1. Chain of Responsibility: A way of passing a request down a chain of handlers until one of them handles the request.
  2. Command: Encapsulate a request as an object, thereby allowing for parameterization of clients with different requests, queuing or logging requests, and supporting undoable operations.
  3. Observer: Define a dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Examples

1. Chain of Responsibility Pattern

Problem Statement: You have a series of request handlers that can process different types of requests. Each handler decides whether to process the request or pass it on to the next handler in the chain.

Exercise:

Create a simple logging system where each log level (INFO, WARNING, ERROR) has its own handler. If a handler cannot process a log message, it passes it to the next handler in the chain.

// Logger.js
class Logger {
  constructor(nextLogger = null) {
    this.nextLogger = nextLogger;
  }

  setNextLogger(nextLogger) {
    this.nextLogger = nextLogger;
  }

  logMessage(level, message) {
    if (this.canHandle(level)) {
      this.write(message);
    } else if (this.nextLogger) {
      this.nextLogger.logMessage(level, message);
    }
  }

  canHandle(level) {
    throw new Error("This method should be overridden by subclasses");
  }

  write(message) {
    throw new Error("This method should be overridden by subclasses");
  }
}

class InfoLogger extends Logger {
  canHandle(level) {
    return level === 'INFO';
  }

  write(message) {
    console.log(`Info: ${message}`);
  }
}

class WarningLogger extends Logger {
  canHandle(level) {
    return level === 'WARNING';
  }

  write(message) {
    console.warn(`Warning: ${message}`);
  }
}

class ErrorLogger extends Logger {
  canHandle(level) {
    return level === 'ERROR';
  }

  write(message) {
    console.error(`Error: ${message}`);
  }
}

**Usage:**

```jsx
// main.js
const errorLogger = new ErrorLogger();
const warningLogger = new WarningLogger(errorLogger);
const infoLogger = new InfoLogger(warningLogger);

infoLogger.logMessage('INFO', 'This is an info message');
infoLogger.logMessage('WARNING', 'This is a warning message');
infoLogger.logMessage('ERROR', 'This is an error message');

Output:

<OutputBlock>
{`Info: This is an info message
Warning: This is a warning message
Error: This is an error message`}
</OutputBlock>

2. Command Pattern

Problem Statement: You want to encapsulate requests as objects, allowing for parameterization of clients with different requests, queuing or logging requests, and supporting undoable operations.

Exercise:

Create a simple text editor that supports basic commands like InsertText, DeleteText, and Undo.

// Command.js
class Command {
  execute() {
    throw new Error("This method should be overridden by subclasses");
  }

  undo() {
    throw new Error("This method should be overridden by subclasses");
  }
}

class InsertTextCommand extends Command {
  constructor(editor, text) {
    this.editor = editor;
    this.text = text;
    this.backup = null;
  }

  execute() {
    this.backup = this.editor.getText();
    this.editor.insert(this.text);
  }

  undo() {
    if (this.backup !== null) {
      this.editor.setText(this.backup);
    }
  }
}

class DeleteTextCommand extends Command {
  constructor(editor, length) {
    this.editor = editor;
    this.length = length;
    this.backup = null;
  }

  execute() {
    this.backup = this.editor.getText();
    this.editor.delete(this.length);
  }

  undo() {
    if (this.backup !== null) {
      this.editor.setText(this.backup);
    }
  }
}

Usage:

// Editor.js
class Editor {
  constructor() {
    this.text = '';
    this.history = [];
  }

  insert(text) {
    this.text += text;
  }

  delete(length) {
    this.text = this.text.slice(0, -length);
  }

  getText() {
    return this.text;
  }

  setText(text) {
    this.text = text;
  }

  executeCommand(command) {
    command.execute();
    this.history.push(command);
  }

  undoLastCommand() {
    if (this.history.length > 0) {
      const lastCommand = this.history.pop();
      lastCommand.undo();
    }
  }
}

Usage:

// main.js
const editor = new Editor();

const insertCmd = new InsertTextCommand(editor, "Hello");
editor.executeCommand(insertCmd);

console.log(editor.getText()); // Output: Hello

const deleteCmd = new DeleteTextCommand(editor, 2);
editor.executeCommand(deleteCmd);

console.log(editor.getText()); // Output: Hel

editor.undoLastCommand();
console.log(editor.getText()); // Output: Hello

3. Observer Pattern

Problem Statement: You want to define a dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Exercise:

Create a simple weather station system where multiple displays (like temperature display and humidity display) update themselves whenever the weather data changes.

// Observer.js
class Subject {
  constructor() {
    this.observers = [];
  }

  registerObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

  notifyObservers() {
    for (const observer of this.observers) {
      observer.update(this.temperature, this.humidity);
    }
  }
}

class WeatherData extends Subject {
  constructor() {
    super();
    this.temperature = 0;
    this.humidity = 0;
  }

  measurementsChanged() {
    this.notifyObservers();
  }

  setMeasurements(temperature, humidity) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.measurementsChanged();
  }
}

Usage:

// Display.js
class Observer {
  update(temperature, humidity) {
    throw new Error("This method should be overridden by subclasses");
  }
}

class CurrentConditionsDisplay extends Observer {
  constructor(weatherData) {
    super();
    this.weatherData = weatherData;
    this.weatherData.registerObserver(this);
  }

  update(temperature, humidity) {
    console.log(`Current conditions: ${temperature}F degrees and ${humidity}% humidity`);
  }
}

class StatisticsDisplay extends Observer {
  constructor(weatherData) {
    super();
    this.weatherData = weatherData;
    this.weatherData.registerObserver(this);
  }

  update(temperature, humidity) {
    console.log(`Statistics: Avg/Max/Min temperature = 80.0/82.0/78.0F`);
  }
}

Usage:

// main.js
const weatherData = new WeatherData();
const currentDisplay = new CurrentConditionsDisplay(weatherData);
const statisticsDisplay = new StatisticsDisplay(weatherData);

weatherData.setMeasurements(80, 65);
weatherData.setMeasurements(82, 70);
weatherData.setMeasurements(78, 90);

Output:

<OutputBlock>
{`Current conditions: 80F degrees and 65% humidity
Statistics: Avg/Max/Min temperature = 80.0/82.0/78.0F
Current conditions: 82F degrees and 70% humidity
Statistics: Avg/Max/Min temperature = 80.0/82.0/78.0F
Current conditions: 78F degrees and 90% humidity
Statistics: Avg/Max/Min temperature = 80.0/82.0/78.0F`}
</OutputBlock>

What's Next?

Now that you have a good understanding of how to apply behavioral patterns, you can explore more advanced topics such as design patterns in software architecture. These patterns provide a robust framework for designing scalable and maintainable systems.

Feel free to experiment with these patterns in your projects and expand your knowledge by reading more about design patterns in various programming languages and frameworks.


PreviousPractical Exercises for Structural PatternsNext Design Patterns in Software Architecture

Recommended Gear

Practical Exercises for Structural PatternsDesign Patterns in Software Architecture