Observer Pattern with Real world example
Observer-pattern

Why do we need Observer pattern?

Think about a scenario where you have a bunch of objects that are dependent on the state of another object. For example, you might have a bunch of buttons on a webpage, and their behavior needs to change depending on whether a user is logged in or not.

Now, you could manually check the login status every time the page loads or every time the user performs an action, but that would quickly become messy and difficult to manage as your codebase grows.

Instead, you can use the Observer pattern! With this pattern, you can define the object whose state is being watched (in this case, the user login status) as the "subject". Then, you define all the other objects that need to be notified when the subject's state changes as "observers".

The subject maintains a list of all the observers, and when its state changes, it automatically notifies all of them. This means you don't have to manually check the login status every time - the Observer pattern takes care of it for you!

The beauty of this pattern is that the subject and the observers are decoupled - they don't need to know anything about each other, and you can add or remove observers without affecting the subject. This makes your code more modular and easier to maintain over time.

The Observer pattern is commonly used in event-driven systems or graphical user interfaces, where changes in one part of the system need to be reflected in other parts. It promotes loose coupling between objects and helps to separate concerns, making it easier to maintain and extend a system over time.

Implementation

Suppose you have an online store that sells electronic gadgets, such as smartphones and laptops. You want to notify your customers when a new gadget is added to your store. You decide to use the Observer pattern to implement this feature. First, you define an IObservable interface that represents the subject (i.e., the store) that customers can observe:

public interface IStoreObservable
{
    void Subscribe(IStoreObserver observer);
    void Unsubscribe(IStoreObserver observer);
}


Next, you define an IStoreObserver interface that represents the observer (i.e., the customer) that receives updates from the store:

public interface IStoreObserver
{
    void Update(string gadget);
}


Then, you implement a Store class that implements the IObservable interface and keeps track of the gadgets in the store:


public class Store : IStoreObservable
{
    private List _observers = new List();
    private List _gadgets = new List();

    public void AddGadget(string gadget)
    {
        _gadgets.Add(gadget);
        NotifyObservers(gadget);
    }

    public void Subscribe(IStoreObserver observer)
    {
        _observers.Add(observer);
    }

    public void Unsubscribe(IStoreObserver observer)
    {
        _observers.Remove(observer);
    }

    private void NotifyObservers(string gadget)
    {
        foreach (var observer in _observers)
        {
            observer.Update(gadget);
        }
    }
}

In this example, the Store class keeps track of the gadgets in the store using a private _gadgets list. Whenever a new gadget is added using the AddGadget method, the gadget is added to the list and the NotifyObservers method is called to update all registered observers (i.e., customers) with the latest gadget added to the store. Finally, you implement two Customer classes that implement the IStoreObserver interface and receive updates whenever a new gadget is added to the store:

public class Customer1 : IStoreObserver
{
    public void Update(string gadget)
    {
        Console.WriteLine("Customer 1 received an update: " + gadget + " is now available!");
    }
}

public class Customer2 : IStoreObserver
{
    public void Update(string gadget)
    {
        Console.WriteLine("Customer 2 received an update: " + gadget + " is now available!");
    }
}


In this example, Customer1 and Customer2 are two classes that represent customers of the online store. They both implement the Update method to receive updates whenever a new gadget is added to the store. To use this code, you can create a Store object and add Customer1 and Customer2 objects as observers. Then, you can add a new gadget to the store using the AddGadget method of the Store object:


Store store = new Store();
Customer1 customer1 = new Customer1();
Customer2 customer2 = new Customer2();
store.Subscribe(customer1);
store.Subscribe(customer2);

store.AddGadget("iPhone 13"); // This should notify both customers that iPhone 13 is now available


Components of Observer Pattern

Subject:

  • The Store class serves as the subject, or the object that is observed.
  • It implements the IStoreObservable interface, which defines the methods that are used to subscribe and unsubscribe observers.
  • The Store class keeps track of its observers using a list of IStoreObserver objects.

Observer:

  • The Customer1 and Customer2 classes serve as the observers, or the objects that observe the subject (i.e., the Store).
  • They implement the IStoreObserver interface, which defines the method that is called when the subject (i.e., the Store) updates.
  • The Update method is called by the Store class whenever a new gadget is added.

Concrete Subject:

  • The Store class is also the concrete subject, or the specific implementation of the subject that is observed.
  • It maintains a list of gadgets that are available in the store.
  • When a new gadget is added to the store, it notifies all of its observers using the NotifyObservers method.

Concrete Observer:

  • The Customer1 and Customer2 classes are also concrete observers, or the specific implementations of the observers that observe the subject.
  • They receive updates when a new gadget is added to the store by implementing the Update method.

To summarize, the Subject is the general concept of the object being observed, while the Concrete Subject is a specific implementation of the Subject that holds the state of the object being observed and notifies its observers of any changes to that state.

Challenges & Limitations

  • Complexity: Implementing the Observer pattern can add complexity to the codebase. The use of multiple objects and interfaces can increase the code's complexity and make it harder to maintain.
  • Performance: The Observer pattern can cause performance issues if the number of observers is too large or if the subject is updated too frequently. This can lead to excessive notifications, which can impact the application's performance.
  • Tight coupling: The Observer pattern can result in tight coupling between the subject and its observers. This can make the code harder to modify and test, as changes to one component can impact other components.
  • Synchronization: In a multi-threaded environment, synchronization can be challenging when implementing the Observer pattern. This is because the observers may need to be notified in a thread-safe manner, which can be difficult to achieve.
  • Memory management: When using the Observer pattern, it's important to manage the memory used by the observers. This can be challenging, especially when dealing with a large number of observers.

Alternatives to Observer pattern

  • Polling: Instead of relying on the subject to notify observers of changes, the observers can periodically check the state of the subject at fixed intervals. This approach can work well for systems with relatively low update rates and where the overhead of polling is acceptable.
  • Callbacks: In this approach, the subject maintains a list of callback functions that are invoked whenever its state changes. Observers can register their own callback functions with the subject, allowing them to be notified of changes without the overhead of maintaining a separate observer list.
  • Mediator pattern: This pattern involves introducing a third party object called a mediator, which acts as an intermediary between the subject and observers. The mediator maintains a list of both the subject and the observers, and handles all communication between them. This approach can be useful for complex systems where there are many dependencies between objects.
  • Event-driven architecture: In this approach, the system is designed around events, which are emitted by objects whenever their state changes. Observers can then subscribe to these events and be notified automatically whenever they occur. This approach is often used in large-scale distributed systems.

Summary

  • The Observer pattern is a design pattern that defines a one-to-many dependency between objects.
  • It allows one object (the subject) to notify a group of other objects (the observers) when its state changes.
  • The pattern is based on the principle of loose coupling, where the subject and observers are decoupled from each other and can be changed independently.
  • The pattern involves the use of an abstract Subject class or interface, which defines the methods for attaching, detaching, and notifying observers.
  • Concrete Subject classes implement the Subject interface or abstract class and maintain a list of observers that are interested in being notified of changes.
  • Observer classes implement the Observer interface or abstract class, which defines the method that the subject calls to notify them of changes.
  • When the state of the subject changes, it calls the notify method, which notifies all registered observers that the state has changed.
  • Observers can then query the subject for information or take other actions based on the new state.
  • The Observer pattern can be used in a variety of situations, such as in graphical user interfaces, stock market applications, and messaging systems.
  • The pattern has some challenges and limitations, including complexity, performance, tight coupling, synchronization, and memory management, which should be considered when using it.