Understanding Behavioral Design Patterns in Software Engineering

Understanding Behavioral Design Patterns in Software Engineering

As we have explored Structural Patterns and Creational Patterns, let's now dive into Behavioral Patterns. As the name suggests, these patterns define how code behaves under certain circumstances.

In this article, we’ll break down some essential behavioral patterns with real-world examples and Python code snippets to help you understand how they work.


1. State Pattern

The State Pattern allows an object to change its behavior when its internal state changes.

Example: Light Bulb

Imagine a light bulb in your home:

  • By default, it is turned off.

  • When you turn on the switch, it gets turned on.

  • Based on its state (ON/OFF), it behaves differently.

When OFF, it does nothing.
When ON, it emits light.

This is an example of the State Pattern, where an object's behavior depends on its internal state.

Python Code Example:

class LightBulb:
    def __init__(self):
        self.state = OffState()  # Initial state is OFF

    def set_state(self, state):
        self.state = state

    def toggle(self):
        self.state.handle(self)

class State:
    def handle(self, bulb):
        pass

class OnState(State):
    def handle(self, bulb):
        print("The light is already ON.")
        bulb.set_state(OffState())  # Switching to OFF state

class OffState(State):
    def handle(self, bulb):
        print("Turning the light ON.")
        bulb.set_state(OnState())  # Switching to ON state

# Usage
bulb = LightBulb()
bulb.toggle()  # Turns ON
bulb.toggle()  # Turns OFF

2. Iterator Pattern

The Iterator Pattern is used to traverse a collection without exposing its underlying structure.

Example: Scrolling Through Friends List on Instagram

When you scroll through your friends list on Instagram, you:

  • Move from one friend to another without worrying about how Instagram fetches the data.

  • Just iterate through the list without needing to understand its internal implementation.

Similarly, when you use a for loop in Python, you iterate through elements without knowing how they are stored in memory.

Python Code Example:

class FriendsList:
    def __init__(self, friends):
        self.friends = friends
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.friends):
            friend = self.friends[self.index]
            self.index += 1
            return friend
        else:
            raise StopIteration

# Usage
friends = FriendsList(["Alice", "Bob", "Charlie"])
for friend in friends:
    print(friend)

3. Strategy Pattern

The Strategy Pattern allows selecting different algorithms (or methods) at runtime based on the situation.

Example: Choosing a Mode of Transportation

If you want to travel from Point A to Point B, you have multiple options:

  • Cycle

  • Bus

  • Train

  • Car

Each mode of transport achieves the same goal but in different ways. This is what the Strategy Pattern is all about—choosing the best method dynamically.

Python Code Example:

class TravelStrategy:
    def travel(self):
        pass

class Cycle(TravelStrategy):
    def travel(self):
        print("Traveling by Cycle.")

class Bus(TravelStrategy):
    def travel(self):
        print("Traveling by Bus.")

class Train(TravelStrategy):
    def travel(self):
        print("Traveling by Train.")

# Usage
def travel_method(strategy: TravelStrategy):
    strategy.travel()

travel_method(Cycle())  # Traveling by Cycle
travel_method(Bus())    # Traveling by Bus
travel_method(Train())  # Traveling by Train

4. Observer Pattern

The Observer Pattern establishes a relationship where multiple objects depend on a single object and get notified when it changes.

Example: A Teacher and Students in a Classroom

In a classroom:

  • The teacher speaks, and students listen.

  • If the teacher changes the topic, students automatically adjust to the new topic.

  • Students are the observers, and the teacher is the subject.

Whenever the teacher (subject) changes the state, all the students (observers) are notified.

Python Code Example:

class Teacher:
    def __init__(self):
        self.students = []

    def register(self, student):
        self.students.append(student)

    def notify(self, message):
        for student in self.students:
            student.update(message)

class Student:
    def __init__(self, name):
        self.name = name

    def update(self, message):
        print(f"{self.name} received message: {message}")

# Usage
teacher = Teacher()
student1 = Student("Alice")
student2 = Student("Bob")

teacher.register(student1)
teacher.register(student2)

teacher.notify("Today's lesson is about Design Patterns.")

Conclusion

By understanding these behavioral patterns, you can write more maintainable and scalable code.

  • State Pattern: Objects behave differently based on their state.

  • Iterator Pattern: Iterating through a collection without knowing its structure.

  • Strategy Pattern: Using different methods to achieve the same goal.

  • Observer Pattern: When one object changes, dependent objects are notified.

These patterns enhance code flexibility and make your software easier to manage. Try implementing them in your own projects!