The Observer Pattern is a fundamental design pattern in C# that facilitates communication and coordination between objects. It enables the creation of reactive systems where changes in one object trigger automatic updates in other dependent objects. In this article, we explore the Observer Design Pattern in C# in depth, examining its implementation, benefits, and real-world applications in C# development.
The Observer Design Pattern is a behavioral software design pattern that establishes a one-to-many relationship between objects. The one-to-many relationship means that a single subject can have multiple observers registered to it. This allows the observers to receive updates from the subject whenever a relevant change occurs. The subject is responsible for managing the list of observers and broadcasting notifications to all subscribed observers.
The pattern consists of two main components: the Subject (also known as the Publisher) and the Observers (also known as the Subscribers or Listeners).
The Subject is responsible for maintaining a list of its dependents (Observers) and notifying them automatically whenever its state changes. It acts as the central authority or source of information. When a change occurs in the Subject, it broadcasts the update to all registered Observers.
Observers are interested in the state changes of the Subject and want to be notified when these changes occur. They "observe" the Subject and take appropriate actions based on the received notifications. The Observers subscribe to the Subject to receive updates.
The Observer Design Pattern enables loose coupling between the Subject and Observers, allowing for flexibility and modularity in the system. It decouples the Subject from the specific implementations of Observers, as the Subject only depends on the Observer interface/base class.
To summarize the workflow of the Observer Design Pattern:
The Subject maintains a list of Observers and provides methods for subscribing and unsubscribing Observers.
Observers implement an interface or inherit from a base class that defines the method(s) to be called by the Subject when an update occurs.
When a state change happens in the Subject, it iterates through its list of Observers and calls the appropriate method(s) on each Observer, passing any necessary data as arguments.
Observers receive the notifications and handle them according to their specific logic.
In the above diagram, the Subject is connected to multiple Observers. When the Subject undergoes a state change, it notifies all the Observers, and each Observer can take appropriate actions based on the received update.
This pattern is also known by other names, such as Producer/Consumer or Publish/Subscribe, as it resembles the concept of a publisher broadcasting messages to multiple subscribers.
Here is an example of an Observer Patter in an Online Auction System
We can illustrate the Observer Design Pattern. Consider a scenario where multiple bidders are interested in a particular item being auctioned. The item being auctioned is the Subject, and the bidders are the Observers.
1. Registration:
The Subject (auction system) maintains a list of registered bidders (observers).
Any new bidder who wants to participate in the auction can register by calling the Register method of the auction system.
2. Unregistration:
If a bidder decides to withdraw from the auction, they can unregister by calling the unregister method of the auction system.
3. State and Notifications:
The auction system has a state representing the current bid value or status of the item being auctioned.
Whenever there is a change in the bid value or status, the auction system notifies all the registered bidders by invoking one of the observer methods.
4. Observer Response:
Upon receiving a notification, each bidder can call one of the auction system's methods to retrieve the updated bid value or status.
In the above image, we observe that the Subject is an object that maintains a list of observers. The Subject provides methods for registering and unregistering observers. In the image, three observers are already registered with the subject. Any new observer who wants to register can call the Register method of the Subject. Similarly, if an observer wants to unregister, they can simply call the unregister method of the subject.
The subject holds a certain state. Whenever there are changes in the state, the subject notifies all the registered observers by invoking one of the observer methods.
Once an observer receives a notification from the subject, it can call one of the subject's methods to retrieve the updated state data.
A real-time example of the Observer Design Pattern:
Let's consider a simple example to understand the Observer Design Pattern. Imagine an online shopping website like Amazon.
Suppose three users are interested in buying a specific mobile phone. Unfortunately, at that moment, the mobile phone is out of stock, meaning it's in a "Not Available" state. However, these three users want to purchase that particular mobile phone. Luckily, the Amazon website provides an option for users to receive notifications when the product becomes available again.
So, these three users register for the notification service, expressing their interest in the mobile phone. They become observers or subscribers to the availability of the product.
After a few days, the mobile phone becomes available and the status changes from "Out of Stock" to "Available." As per the Observer Design Pattern, Amazon sends notifications to all the registered users who showed interest in the product. This way, the users are informed about the availability of the mobile phone.
In this example, the subject is the mobile phone, and the three users are the observers. The users are registered with the notification service, and when the state of the mobile phone changes, the subject (Amazon) notifies all the subscribed observers about the update.
The Observer Design Pattern simplifies the communication between the subject and the observers, allowing for efficient updates and notifications without tightly coupling the components together.
A step-by-step implementation of the Observer Design Pattern in C#.
Step 1: Creating the Subject interface
The code defines an interface called ISubject, which declares the methods RegisterObserver, RemoveObserver, and NotifyObservers. This interface represents the Subject in the Observer Design Pattern.
namespace ObserverDesignPattern
{
public interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
}
Step 2: Creating Concrete Subject
The code defines a class called Subject that implements the ISubject interface. It has private fields for product information such as name, price, and availability. It also maintains a list of observers and provides methods to register, remove, and notify observers. The NotifyObservers method is responsible for iterating through the list of observers and calling their update method.
using System;
using System.Collections.Generic;
namespace ObserverDesignPattern
{
public class Subject : ISubject
{
private List<IObserver> observers = new List<IObserver>();
private string ProductName { get; set; }
private int ProductPrice { get; set; }
private string Availability { get; set; }
public Subject
(
string productName,
int productPrice,
string availability)
{
ProductName = productName;
ProductPrice = productPrice;
Availability = availability;
}
public string getAvailability()
{
return Availability;
}
public void setAvailability(string availability)
{
this.Availability = availability;
Console.WriteLine("Availability changed from Out of Stock to
Available.");
NotifyObservers();
}
public void RegisterObserver(IObserver observer)
{
Console.WriteLine("Observer Added : " +
((Observer)observer).UserName );
observers.Add(observer);
}
public void AddObservers(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
public void NotifyObservers()
{
Console.WriteLine("Product Name :"
+ ProductName + ", product Price : "
+ ProductPrice + " is Now available. So
notifying all Registered users ");
Console.WriteLine();
foreach (IObserver observer in observers)
{
observer.update(Availability);
}
}
}
}
Step 3: Creating Observer Interface
The code defines an interface called IObserver that declares the update method. This interface represents the Observer in the Observer Design Pattern.
namespace ObserverDesignPattern
{
public interface IObserver
{
void update(string availability);
}
}
Step 4: Creating Concrete Observer
The code defines a class called Observer that implements the IObserver interface. It has a UserName property and a reference to a Subject object. When an observer is created, it registers itself with the subject using the RegisterObserver method. The update method is called by the subject to notify the observer about changes in availability.
using System;
namespace ObserverDesignPattern
{
public class Observer : IObserver
{
public string UserName { get; set; }
public Observer(string userName, ISubject subject)
{
UserName = userName;
subject.RegisterObserver(this);
}
public void update(string availabiliy)
{
Console.WriteLine("Hello " + UserName + ", Product is now " +
availabiliy + " on Amazon");
}
}
}
The Main method sets up a test scenario:
It creates a Subject object named RedMI representing a Red MI Mobile with an initial state of "Out Of Stock."
Three Observer objects (user1, user2, and user3) are created and registered with the RedMI subject.
The current availability state of the product is displayed.
The setAvailability method is called on the RedMI object to change the availability to "Available."
using System;
namespace ObserverDesignPattern
{
class Program
{
static void Main(string[] args)
{
//Create a Product with Out Of Stock Status
Subject RedMI = new Subject("RedMI", 10000, "Out Of
Stock");
//User Anurag will be created and user1 object will be
registered to the subject
Observer user1 = new Observer("Anand", RedMI);
//User Pranaya will be created and user1 object will be
registered to the subject
Observer user2 = new Observer("Rahul", RedMI);
//User Priyanka will be created and user3 object will be
registered to the subject
Observer user3 = new Observer("Aishwarya", RedMI);
Console.WriteLine("RedMI current state : " +
RedMI.getAvailability());
Console.WriteLine();
// Now product is available
RedMI.setAvailability("Available");
Console.Read();
}
}
}
Output:
Observer Added: Anand
Observer Added: Rahul
Observer Added: Aishwarya
RedMI current state: Out Of Stock
Availability changed from Out Of Stock to Available.
Product Name:RedMI, Product Price: 10000 is Now available. So notifying all Registered users
Hello Anand, Product is now Available on Amazon
Hello Rahul, Product is now Available on Amazon
Hello Aishwarya, Product is now Available on Amazon
Applicability of Observer Pattern:
The Observer Design Pattern is applicable in the following scenarios:
1. Multiple dependencies on one object: When multiple objects depend on the state of another object. It establishes a one-to-many relationship, where changes in the object's state automatically update the attached objects (achieving loose coupling).
2. Notifications, emails, and messages: It is useful for sending notifications, emails, messages, or any kind of event updates. For example, when you subscribe to a website, you receive notifications about new events on that website.
3. Tight coupling between objects: If objects need to be closely connected, such that changes in one object's state should reflect in another object.
4. Dynamic subscriber list: When the list of subscribers is dynamic, allowing subscribers to join or leave the subscription at any time.
The Observer Design Pattern simplifies the communication and updates between objects, especially in scenarios where multiple objects need to stay synchronized with changes in the state of a subject-object. It provides flexibility and loose coupling, allowing for efficient notifications and updates in a dynamic system.
Advantages of Observer Pattern in C#:
The Observer pattern provides you with the following advantages:
1. Loose Coupling:
The Observer pattern promotes loose coupling between interacting objects.
Objects can communicate and interact without having detailed knowledge about each other's implementation.
Changes in one object do not require modifications in the Subject or Observer classes.
2. Effective Data Communication:
It provides an efficient mechanism for sending data to other objects.
Observers receive updates from the Subject without any changes needed in the Subject or Observer classes.
Data can be effectively transmitted and shared between objects.
3. Dynamic Addition/Removal of Observers:
Observers can be added or removed from the system at any point in time.
This flexibility allows for dynamic changes in the number and type of observers.
The system can adapt to varying requirements and easily manage observer subscriptions.
Disadvantages of Observer Pattern in C#:
The following are the disadvantages of the Observer pattern:
1. Inheritance Requirement:
The Observer interface needs to be implemented by Concrete Observers.
This involves inheritance and may limit flexibility in certain scenarios where the composition is preferred over the inheritance.
2. Complexity and Performance Issues:
Incorrect implementation of the Observer pattern can introduce complexity and potential performance issues.
Care must be taken to ensure efficient updates and avoid unnecessary overhead caused by excessive notifications.
3. Notification Management:
In software applications with a large number of observers, managing notifications can become challenging.
It requires proper handling and coordination to ensure that notifications are sent only when necessary and without causing delays or bottlenecks.
Conclusion
The Observer Pattern in C# is a powerful tool for designing reactive systems, event-driven architectures, and GUI frameworks. By using the Observer Pattern, you can achieve a decoupled and flexible design, where subjects and observers can be easily added, removed, or modified without affecting the rest of the system. This promotes extensibility and maintainability, as new observers can be introduced without modifying the subject or other existing observers.
Comments