Design Pattern (23) Observer Pattern: Smart Security System One-to-Many Notification Mechanism, Implementing Real-time Alert Broadcasting

You can download the Design Pattern series code from this design_pattern repo.

Requirements

Imagine you are designing a home security system. Our task is to design a Security System Panel, which acts as the brain of the entire security system.

Specific requirements are as follows:

  • The panel monitors different sensors, such as smoke detectors or door/window sensors
  • When an alarm is triggered, the panel needs to immediately notify all registered devices
  • Notification targets include different types of devices such as tablets, iOS phones, and Android phones
  • Devices can dynamically join or leave the notification list, ensuring system flexibility

This scenario is very common in real life. Whenever sensors detect abnormal conditions, we want all related devices to receive alert notifications in real-time.

Object-Oriented Analysis (OOA)

After understanding the requirements, let’s begin object-oriented analysis. Through analysis, we can see the system structure and relationships between components more clearly.

Recognizing Forces

When designing this security system, what problems would we encounter if we don’t use appropriate design patterns? Let’s analyze the potential challenges:

  1. High Coupling

    If the panel directly interacts with each device, the code becomes difficult to maintain. Imagine that every time we add or remove devices, we need to modify the panel’s core logic, making the system very fragile.

  2. Lack of Flexibility

    When we want to add a new type of device, we must modify existing code. This violates the Open-Closed Principle (OCP) in software design, which states ā€œopen for extension, closed for modification.ā€

  3. Inconsistent Notifications

    In emergency situations, how do we ensure that every device receives alert notifications correctly and timely? If notification mechanisms don’t have unified standards, omissions or errors easily occur.

These problems all point to a core issue: we need a solution that can effectively manage ā€œone-to-manyā€ relationships.


Applying Observer Pattern (Solution) to Get New Context (Resulting Context)

After completing OOA analysis and recognizing Forces, we now clearly understand the entire problem context. Next, let’s apply the Observer Pattern to solve this problem.

Core Concepts of Observer Pattern

First, let’s look at the standard UML structure of Observer Pattern:

Observer Pattern provides an elegant one-to-many notification mechanism. When the subject’s state changes, it automatically notifies all subscribed observers.

Observer Pattern, along with Strategy Pattern and State Pattern, belongs to behavioral design patterns, but Observer Pattern focuses on communication between objects and event handling mechanisms.

Let’s understand each role’s responsibilities:

  • Subject: Security system panel, responsible for managing all devices and sending notifications when alarms are triggered
  • Observer: Various devices, such as tablets, iOS and Android phones, responsible for receiving notifications and executing corresponding operations
  • ConcreteSubject: Actual security system panel implementation, containing complete alarm logic
  • ConcreteObserver: Specific device implementations, such as Android devices or iOS devices

Application to Our Security System

Now let’s apply Observer Pattern to our security system application:

Through this design, we solve all previously identified problems. The panel no longer needs to directly depend on each specific device, but communicates through unified interfaces.

Implementation

Now let’s convert Observer Pattern design into actual code. We’ll implement each component step by step and explain their functionality.

Subject Interface: AlarmSystem

First define the subject interface, which specifies basic functionality that alarm systems must have:

interface AlarmSystem {
    fun addObserver(observer: Device)
    fun removeObserver(observer: Device)
    fun notifyObservers(alarmMessage: String)
}

This interface defines three core methods: add observer, remove observer, and notify all observers.

Observer Interface: Device

Next define the observer interface, representing all devices that can receive alarm notifications:

interface Device {
    fun onAlarmTriggered(alarmMessage: String)
}

Each device must implement the onAlarmTriggered method to handle received alarm messages.

ConcreteSubject: SecurityPanel

Now implement the concrete security system panel:

class SecurityPanel : AlarmSystem {
    private val devices = mutableListOf<Device>()

    override fun addObserver(observer: Device) {
        devices.add(observer)
    }

    override fun removeObserver(observer: Device) {
        devices.remove(observer)
    }

    override fun notifyObservers(alarmMessage: String) {
        for (device in devices) {
            device.onAlarmTriggered(alarmMessage)
        }
    }

    fun triggerAlarm(zone: String) {
        val message = "Alarm triggered in $zone!"
        println("Panel notification: $message")
        notifyObservers(message)
    }
}

SecurityPanel maintains a device list and provides functionality to add, remove, and notify devices. When an alarm is triggered, it automatically notifies all registered devices.

ConcreteObserver: Various Devices

Next implement different types of devices:

class Tablet : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("Tablet received notification: $alarmMessage")
    }
}

class IOSDevice : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("iOS device received notification: $alarmMessage")
    }
}

class AndroidDevice : Device {
    override fun onAlarmTriggered(alarmMessage: String) {
        println("Android device received notification: $alarmMessage")
    }
}

Each device type implements the same interface but can execute different processing logic according to its own characteristics.

Client Test Program

Finally, let’s see how to use this system:

fun main() {
    val securityPanel = SecurityPanel()

    val tablet = Tablet()
    val iosDevice = IOSDevice()
    val androidDevice = AndroidDevice()

    // add observers
    securityPanel.addObserver(tablet)
    securityPanel.addObserver(iosDevice)
    securityPanel.addObserver(androidDevice)

    // trigger alarm
    securityPanel.triggerAlarm("Living Room")
    securityPanel.triggerAlarm("Kitchen")

    // remove observer
    securityPanel.removeObserver(androidDevice)
    securityPanel.triggerAlarm("Bedroom")
}

This test program demonstrates the complete system flow: registering devices, triggering alarms, and dynamic device removal functionality.

Execution Results

Let’s see this program’s execution output:

Panel notification: Alarm triggered in Living Room!
Tablet received notification: Alarm triggered in Living Room!
iOS device received notification: Alarm triggered in Living Room!
Android device received notification: Alarm triggered in Living Room!

Panel notification: Alarm triggered in Kitchen!
Tablet received notification: Alarm triggered in Kitchen!
iOS device received notification: Alarm triggered in Kitchen!
Android device received notification: Alarm triggered in Kitchen!

Panel notification: Alarm triggered in Bedroom!
Tablet received notification: Alarm triggered in Bedroom!
iOS device received notification: Alarm triggered in Bedroom!

From the output results, we can see that after the Android device was removed, the last alarm only notified the tablet and iOS device. This perfectly demonstrates the Observer Pattern’s dynamic management capability.

Conclusion

Through Observer Pattern implementation, we successfully constructed a flexible and extensible security system notification mechanism. This solution brings multiple significant advantages:

Main Advantages

  • Low Coupling: The panel communicates with various devices through abstract interfaces, reducing dependency relationships between them
  • High Extensibility: New device types can be easily added without modifying existing panel logic
  • Dynamic Management: Devices can dynamically join or leave notification lists at runtime
  • Complies with OCP Principle: Open for extension, closed for modification, conforming to software design best practices

Practical Application Scenarios

Observer Pattern has wide applications in the real world, particularly suitable for the following scenarios:

  • Real-time Alert Systems: Like our implemented security system, needing to notify multiple devices simultaneously
  • Message Push Systems: Applications needing to send notifications to multiple users simultaneously
  • Event Distribution Systems: Event handling in GUI programs, or state updates in games
  • Data Monitoring Systems: When data changes occur, needing to update multiple display components

The core value of this pattern lies in establishing a standardized communication mechanism, allowing systems to elegantly handle one-to-many notification requirements.

In the behavioral design pattern learning path, Observer Pattern works together with Command Pattern, Mediator Pattern and other patterns to collectively construct complete object interaction and communication frameworks. Mastering Observer Pattern will significantly enhance your ability to design real-time responsive systems.




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Claude Code ä½æē”ØęŠ€å·§čˆ‡ęœ€ä½³åÆ¦čø - Tips and Best Practices
  • šŸ¤– AI Agent Series (Part 1): Understanding the Core Interaction Logic of LLM, RAG, and MCP
  • šŸ’” Managing Multiple GitHub Accounts on One Computer: The Simplest SSH Configuration Method
  • šŸš€ How to Use Excalidraw AI to Quickly Generate Professional Diagrams and Boost Work Efficiency!
  • Complete macOS Development Environment Setup Guide: Mobile Development Toolchain Configuration Tutorial