Deep Dive into Behavioral Patterns - The Observer Pattern.

Hey software designers! Today, we’re diving into the Observer pattern. This pattern is essential for establishing a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Let’s explore its workings, benefits, and real-world applications with detailed examples.

What is the Observer Pattern?

The Observer pattern is a behavioral design pattern that establishes a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. It is useful for implementing distributed event-handling systems.

Real-World Scenario

Imagine you’re developing a weather monitoring system where multiple display units need to show the latest weather updates. When the weather changes, all display units should be notified and updated automatically.

The Problem

When multiple objects depend on the state of another object, directly updating the dependents can lead to a tightly coupled and inflexible codebase. This approach makes it difficult to add or remove dependents without modifying the existing code.

Without Observer Pattern

class WeatherStation
  attr_accessor :temperature

  def initialize
    @temperature = 0
  end

  def set_temperature(temp)
    @temperature = temp
    update_displays
  end

  def update_displays
    # Update all displays
  end
end

class Display
  def update(temp)
    puts "Current temperature: #{temp}°C"
  end
end

station = WeatherStation.new
display1 = Display.new
display2 = Display.new

station.set_temperature(25)

Drawbacks: The code is tightly coupled to specific implementations and hard to extend to support new displays.

The Solution: Observer Pattern

Using the Observer pattern, we can decouple the subject from its observers, promoting flexibility and scalability.

With Observer Pattern

Step 1: Define the Observer Interface

class Observer
  def update(state)
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

Step 2: Create Concrete Observers

class Display < Observer
  def update(state)
    puts "Current temperature: #{state}°C"
  end
end

Step 3: Define the Subject Interface

class Subject
  def initialize
    @observers = []
  end

  def add_observer(observer)
    @observers << observer
  end

  def remove_observer(observer)
    @observers.delete(observer)
  end

  def notify_observers
    @observers.each { |observer| observer.update(@state) }
  end
end

Step 4: Create Concrete Subjects

class WeatherStation < Subject
  attr_accessor :temperature

  def set_temperature(temp)
    @temperature = temp
    @state = temp
    notify_observers
  end
end

Step 5: Implement Client Code

station = WeatherStation.new
display1 = Display.new
display2 = Display.new

station.add_observer(display1)
station.add_observer(display2)

station.set_temperature(25)

Benefits: Decouples the subject from its observers, promoting flexibility and scalability.

Real-World Benefits

Scenario: Adding New Observers

Imagine you need to add new observers (e.g., mobile app display). Using the Observer pattern, you can easily add new observers without modifying the existing code.

Without Observer Pattern:

class WeatherStation
  def update_displays
    display1.update(@temperature)
    display2.update(@temperature)
    mobile_app.update(@temperature)
  end
end

class MobileApp
  def update(temp)
    puts "Mobile app display: #{temp}°C"
  end
end

mobile_app = MobileApp.new
station.update_displays

Drawbacks: Tightly coupled code that is difficult to maintain and extend.

With Observer Pattern:

class MobileApp < Observer
  def update(state)
    puts "Mobile app display: #{state}°C"
  end
end

mobile_app = MobileApp.new
station.add_observer(mobile_app)

station.set_temperature(25)

Benefits: Clean, maintainable code with high flexibility and extensibility.

Conclusion

The Observer pattern is a powerful tool for establishing a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. It promotes flexibility, scalability, and maintainability in your code. By using the Observer pattern, you can easily manage and extend the relationships between objects without tightly coupling the code. Incorporate the Observer pattern into your design strategies to build more robust and adaptable software sys…

Stay tuned for more insights into software design principles and patterns.

Thôi Lo Code Đi Kẻo Sếp nạt!!