Back to: Design Patterns in C# With Real-Time Examples
Real-Time Examples of Observer Design Pattern in C#
In this article, I will discuss the Real-Time Examples of Observer Design Pattern in C#. Please read our previous article discussing the basic concepts of the Observer Design Pattern in C#. The Observer design pattern is particularly valuable when a change in one object (the subject) needs to be communicated to one or more other objects (the observers) without the subject needing to know about these observers explicitly. At the end of this article, you will understand the following real-time example using the Observer Design Pattern in C#.
- JOB Portal System
- Server Monitoring System
- Weather Monitoring System
- Social Media Notification System
- Stock Market Tracking System
- E-Commerce System for Product Price Updates
- A News Publishing System
Real-Time Example of Observer Design Pattern in C# – JOB Portal
Let’s understand a real-time example, i.e., a Job Portal. In this scenario, when a company posts a new job listing, all registered job seekers who have expressed interest in such job types/categories should get a notification. Let us proceed and see how we can implement this using the Observer Design Pattern in C#.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { JobPostings jobPostings = new JobPostings(); JobSeeker Pranaya = new JobSeeker("Pranaya"); JobSeeker Anurag = new JobSeeker("Anurag"); JobSeeker Kumar = new JobSeeker("Kumar"); JobSeeker Rout = new JobSeeker("Rout"); //Attach jobPostings.Attach(Pranaya); jobPostings.Attach(Anurag); jobPostings.Attach(Kumar); jobPostings.Attach(Rout); //Detach jobPostings.Detach(Anurag); jobPostings.AddJob(new Job("Software Developer at Microsoft")); Console.ReadKey(); } } // Observer Interface public interface IJobSeeker { void Notify(Job job); } // Concrete Observer public class JobSeeker : IJobSeeker { public string Name { get; set; } public JobSeeker(string name) { Name = name; } public void Notify(Job job) { Console.WriteLine($"Hi {Name}, new job posted: {job.Description}"); } } // Subject Interface public interface IJobPostings { void Attach(IJobSeeker jobSeeker); void Detach(IJobSeeker jobSeeker); void Notify(Job job); } // Concrete Subject public class JobPostings : IJobPostings { private List<IJobSeeker> _jobSeekers = new List<IJobSeeker>(); public void Attach(IJobSeeker jobSeeker) { _jobSeekers.Add(jobSeeker); } public void Detach(IJobSeeker jobSeeker) { _jobSeekers.Remove(jobSeeker); } public void Notify(Job job) { foreach (var jobSeeker in _jobSeekers) { jobSeeker.Notify(job); } } public void AddJob(Job job) { Notify(job); } } public class Job { public string Description { get; set; } public Job(string description) { Description = description; } } }
In the example above:
- JobPostings (Subject) maintains a list of all the JobSeeker (Observer) instances interested in job notifications.
- Whenever a new job is added using the AddJob method, all registered job seekers are notified about the job posting.
- When you run the Main method, “Pranaya”, “Kumar”, and “Rout” are notified about the “Software Developer at Microsoft” job posting.
Output:
Real-Time Example of Observer Design Pattern in C#: Server Monitoring System
In this context, we have a system that monitors the health of servers. Whenever a server’s status changes (e.g., from ‘Active’ to ‘Down’), the monitoring system should notify the IT team or any registered observer.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { var server = new Server("Server1001", "Active"); var itTeam = new ITTeam(); var adminTeam = new AdminTeam(); server.Attach(itTeam); server.Attach(adminTeam); Console.WriteLine($"Initial Status: {server.Status}"); // Simulating a status change server.Status = "Down"; Console.ReadKey(); } } // Observer Interface public interface IObserver { void Update(string serverName, string status); } // Concrete Observer: IT Team public class ITTeam : IObserver { public void Update(string serverName, string status) { Console.WriteLine($"IT Team Alert: Server {serverName} is now {status}"); } } // Concrete Observer: Admin Team public class AdminTeam : IObserver { public void Update(string serverName, string status) { Console.WriteLine($"Admin Team Alert: Please note that server {serverName} is now {status}"); } } // Subject Interface public interface IServerStatusMonitor { void Attach(IObserver observer); void Detach(IObserver observer); void Notify(); } // Concrete Subject public class Server : IServerStatusMonitor { private List<IObserver> _observers = new List<IObserver>(); public string ServerName { get; set; } private string _status; public Server(string serverName, string status) { ServerName = serverName; _status = status; } public string Status { get { return _status; } set { if (_status != value) { _status = value; Notify(); } } } public void Attach(IObserver observer) { _observers.Add(observer); } public void Detach(IObserver observer) { _observers.Remove(observer); } public void Notify() { foreach (var observer in _observers) { observer.Update(ServerName, _status); } } } }
In the example above:
- Server (the subject) maintains a list of observers (ITTeam and AdminTeam) that wish to be informed about the status changes.
- The IT and Admin teams (observers) register themselves with the server they want to monitor.
- The monitoring system notifies all registered teams when the server’s status changes.
Output:
Real-Time Example of Observer Design Pattern in C#: Weather Monitoring System
In this example, a WeatherStation (subject) will provide updates about the weather, and multiple Display units (observers), like CurrentConditionsDisplay, ForecastDisplay, etc., will update themselves based on the data received.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { WeatherStation weatherStation = new WeatherStation(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(); ForecastDisplay forecastDisplay = new ForecastDisplay(); weatherStation.RegisterObserver(currentDisplay); weatherStation.RegisterObserver(forecastDisplay); weatherStation.SetMeasurements(25, 65, 1012); weatherStation.SetMeasurements(22, 70, 995); Console.ReadKey(); } } // Observer Interface public interface IDisplayObserver { void Update(float temperature, float humidity, float pressure); } // Subject Interface public interface IWeatherSubject { void RegisterObserver(IDisplayObserver observer); void RemoveObserver(IDisplayObserver observer); void NotifyObservers(); } // Concrete Subject public class WeatherStation : IWeatherSubject { private List<IDisplayObserver> _observers; private float _temperature; private float _humidity; private float _pressure; public WeatherStation() { _observers = new List<IDisplayObserver>(); } public void RegisterObserver(IDisplayObserver observer) { _observers.Add(observer); } public void RemoveObserver(IDisplayObserver observer) { _observers.Remove(observer); } public void NotifyObservers() { foreach (var observer in _observers) { observer.Update(_temperature, _humidity, _pressure); } } public void MeasurementsChanged() { NotifyObservers(); } public void SetMeasurements(float temperature, float humidity, float pressure) { _temperature = temperature; _humidity = humidity; _pressure = pressure; MeasurementsChanged(); } } // Concrete Observer public class CurrentConditionsDisplay : IDisplayObserver { public void Update(float temperature, float humidity, float pressure) { Console.WriteLine($"Current Conditions: {temperature}°C and {humidity}% humidity"); } } public class ForecastDisplay : IDisplayObserver { public void Update(float temperature, float humidity, float pressure) { Console.WriteLine($"Forecast: {(pressure < 1000 ? "Rain" : "Sunny")} with {temperature}°C"); } } }
When you run this program, you’ll see that the two display units (CurrentConditionsDisplay and ForecastDisplay) update themselves based on the measurements set in the WeatherStation, as shown in the image below.
This design allows for adding other display types in the future without needing to modify the existing WeatherStation code. It’s a classic example of the Observer Pattern, where subjects and observers are decoupled, making the system more modular and easier to extend.
Real-Time Example of Observer Design Pattern in C#: Social Media Notification System
Let’s take the example of a social media platform where users can follow other users and get notified when someone they follow posts new content. Imagine a platform similar to Twitter where users can post messages (tweets), and followers get notified of these messages.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { User Pranaya = new User("Pranaya"); User Rout = new User("Rout"); Follower Anurag = new Follower("Anurag"); Follower Mohanty = new Follower("Mohanty"); Pranaya.Follow(Anurag); // Anurag follows Pranaya Rout.Follow(Anurag); // Anurag follows Rout Pranaya.Follow(Mohanty); // Mohanty follows Pranaya Pranaya.PostMessage("Hello from Pranaya!"); Rout.PostMessage("Rout's first post."); Console.ReadKey(); } } // Observer Interface public interface IFollower { void Update(User user, string message); } // Concrete Observer: Follower public class Follower : IFollower { public string Name { get; private set; } public Follower(string name) { Name = name; } public void Update(User user, string message) { Console.WriteLine($"{Name} received a new post from {user.Name}: {message}"); } } // Subject Interface public interface IUser { void Follow(IFollower follower); void Unfollow(IFollower follower); void NotifyFollowers(string message); } // Concrete Subject: User public class User : IUser { public string Name { get; private set; } private List<IFollower> _followers = new List<IFollower>(); public User(string name) { Name = name; } public void Follow(IFollower follower) { _followers.Add(follower); } public void Unfollow(IFollower follower) { _followers.Remove(follower); } public void PostMessage(string message) { Console.WriteLine($"{Name} posted: {message}"); NotifyFollowers(message); } public void NotifyFollowers(string message) { foreach (var follower in _followers) { follower.Update(this, message); } } } }
In this program:
- User (the subject) represents a social media user who can post messages.
- Follower (the observer) represents users who follow other users to get updates.
- All followers are notified when a User posts a message using the PostMessage method.
When you run this program, the output will demonstrate that Anurag, who follows both Pranaya and Rout, will receive updates from both. In contrast, Mohanty, who only follows Pranaya, will receive updates from him.
This example shows the power of the Observer pattern in building systems where one entity’s state changes (e.g., posting a new message) must be broadcast to many other entities without a direct dependency.
Real-Time Example of Observer Design Pattern in C#: Stock Market Tracking System
Traders subscribe to stock market tickers for specific stocks. When the stock price changes, the ticker notifies all the traders who have subscribed to that particular stock.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { Stock HSBLStock = new Stock("HSBL", 150.00); Trader Pranaya = new Trader("Pranaya"); Trader Kumar = new Trader("Kumar"); Trader Rout = new Trader("Rout"); //Register Traders HSBLStock.RegisterTrader(Pranaya); HSBLStock.RegisterTrader(Kumar); HSBLStock.RegisterTrader(Rout); //UnRegister HSBLStock.UnregisterTrader(Kumar); // Simulate a price change HSBLStock.Price = 152.50; Console.ReadKey(); } } // Observer Interface public interface ITrader { void Update(Stock stock); } // Concrete Observer: Trader public class Trader : ITrader { public string Name { get; set; } public Trader(string name) { Name = name; } public void Update(Stock stock) { Console.WriteLine($"Notifying {Name} of {stock.Symbol}'s price change to {stock.Price}"); } } // Subject Interface public interface IStockTicker { void RegisterTrader(ITrader trader); void UnregisterTrader(ITrader trader); void Notify(); } // Concrete Subject: Stock public class Stock : IStockTicker { private List<ITrader> _traders = new List<ITrader>(); public string Symbol { get; private set; } private double _price; public Stock(string symbol, double price) { Symbol = symbol; _price = price; } public double Price { get { return _price; } set { if (_price != value) { _price = value; Notify(); } } } public void RegisterTrader(ITrader trader) { _traders.Add(trader); } public void UnregisterTrader(ITrader trader) { _traders.Remove(trader); } public void Notify() { foreach (var trader in _traders) { trader.Update(this); } } } }
In this example:
- Stock (the subject) represents an individual stock on the market. It maintains a list of traders (observers) interested in price changes for that stock.
- Trader (the observer) represents an individual or entity interested in receiving updates for specific stocks.
- All registered traders are notified when the stock’s price changes (e.g., HSBLStock.Price = 152.50).
Output:
The Observer pattern is particularly useful here as the stock can easily notify an unpredictable number of entities (traders) without knowing anything about them except that they’re interested in the stock’s price changes.
Real-Time Example of Observer Design Pattern in C#: E-Commerce System for Product Price Updates
Consider an e-commerce platform where customers can track price drops on particular products. When a product’s price changes, all customers who have expressed interest in that product (by adding it to a “watchlist” or “wishlist”) should be notified.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { Product SamsungMobile = new Product("Samsung Mobile", 1500.00); Customer Pranaya = new Customer("Pranaya"); Customer Priyanka = new Customer("Priyanka"); Customer Rout = new Customer("Rout"); //AddWatcher SamsungMobile.AddWatcher(Pranaya); // Pranaya wants to watch the price of the Samsung Mobile SamsungMobile.AddWatcher(Priyanka); // Priyanka wants to watch the price of the Samsung Mobile SamsungMobile.AddWatcher(Rout); // Rout also wants to watch the price of the Samsung Mobile //RemoveWatcher SamsungMobile.RemoveWatcher(Priyanka); // Simulating a price change SamsungMobile.Price = 1000.00; Console.ReadKey(); } } // Observer Interface public interface ICustomer { void Notify(Product product); } // Concrete Observer: Customer public class Customer : ICustomer { public string CustomerName { get; private set; } public Customer(string name) { CustomerName = name; } public void Notify(Product product) { Console.WriteLine($"{CustomerName}, the price of {product.ProductName} has changed to ${product.Price}!"); } } // Subject Interface public interface IProduct { void AddWatcher(ICustomer customer); void RemoveWatcher(ICustomer customer); void NotifyWatchers(); } // Concrete Subject: Product public class Product : IProduct { private List<ICustomer> _customers = new List<ICustomer>(); public string ProductName { get; private set; } private double _price; public Product(string name, double price) { ProductName = name; _price = price; } public double Price { get { return _price; } set { if (_price != value) { _price = value; NotifyWatchers(); } } } public void AddWatcher(ICustomer customer) { _customers.Add(customer); } public void RemoveWatcher(ICustomer customer) { _customers.Remove(customer); } public void NotifyWatchers() { foreach (var customer in _customers) { customer.Notify(this); } } } }
In this scenario:
- Product (the subject) represents an item listed on the e-commerce platform. It keeps track of the customers (observers) who want updates on price changes.
- Customer (the observer) represents a customer who may be interested in price changes for specific products.
- When the price of a product changes, all registered customers are notified.
Output:
This model makes it simple to manage and notify a dynamic list of customers interested in any given product’s price changes. The Observer pattern ensures that the system remains flexible and can easily accommodate more features like sending notifications through email, SMS, or other communication methods.
Real-Time Example of Observer Design Pattern in C#: A News Publishing System
In a news publishing system, subscribers may be interested in getting updates on specific topics. When a new article related to a topic they’ve subscribed to gets published, they should be notified.
using System; using System.Collections.Generic; namespace ObserverDesignPattern { public class Program { public static void Main() { NewsPublisher techCrunch = new NewsPublisher(); Subscriber Pranaya = new Subscriber("Pranaya"); Subscriber Kumar = new Subscriber("Kumar"); Subscriber Rout = new Subscriber("Rout"); Subscriber Hina = new Subscriber("Hina"); //AddSubscriber techCrunch.AddSubscriber("Tech", Pranaya); techCrunch.AddSubscriber("Finance", Kumar); techCrunch.AddSubscriber("Tech", Rout); techCrunch.AddSubscriber("Sports", Rout); techCrunch.AddSubscriber("Sports", Hina); //RemoveSubscriber techCrunch.RemoveSubscriber("Sports", Hina); // A new tech article is published techCrunch.Publish(new Article { Title = "Chandrayaan-3 Launched to Moon", Topic = "Tech" }); // A new finance article is published techCrunch.Publish(new Article { Title = "Stock Market Hits All Time High", Topic = "Finance" }); // A new finance article is published techCrunch.Publish(new Article { Title = "Cricked World Cup in 2023", Topic = "Sports" }); Console.ReadKey(); } } // Observer Interface public interface ISubscriber { void Notify(Article article); } // Concrete Observer: Subscriber public class Subscriber : ISubscriber { public string SubscriberName { get; private set; } public Subscriber(string name) { SubscriberName = name; } public void Notify(Article article) { Console.WriteLine($"{SubscriberName} received a new article! Title: {article.Title} - Topic: {article.Topic}"); } } // Subject Interface public interface INewsPublisher { void AddSubscriber(string topic, ISubscriber subscriber); void RemoveSubscriber(string topic, ISubscriber subscriber); void Publish(Article article); } // Concrete Subject: NewsPublisher public class NewsPublisher : INewsPublisher { private Dictionary<string, List<ISubscriber>> topicSubscribers = new Dictionary<string, List<ISubscriber>>(); public void AddSubscriber(string topic, ISubscriber subscriber) { if (!topicSubscribers.ContainsKey(topic)) { topicSubscribers[topic] = new List<ISubscriber>(); } topicSubscribers[topic].Add(subscriber); } public void RemoveSubscriber(string topic, ISubscriber subscriber) { if (topicSubscribers.ContainsKey(topic)) { topicSubscribers[topic].Remove(subscriber); } } public void Publish(Article article) { if (topicSubscribers.ContainsKey(article.Topic)) { foreach (var subscriber in topicSubscribers[article.Topic]) { subscriber.Notify(article); } } } } public class Article { public string Title { get; set; } public string Topic { get; set; } } }
In this example:
- NewsPublisher (the subject) represents a news publisher that manages subscribers based on topics and sends updates when a new article on a topic is published.
- Subscriber (the observer) represents individuals or entities that subscribe to specific topics and receive article notifications.
When you run this program, you’ll notice Pranaya and Rout get notified about the new tech article, while Kumar gets notified about the finance article. Rout also has a subscription to Sports and receives notifications of sports-related articles.
This design benefits a content subscription model, enabling efficient content distribution to interested subscribers based on their preferences.
When to use the Observer Design Pattern in C#?
The Observer design pattern is particularly valuable when a change in one object (the subject) needs to be communicated to one or more other objects (the observers) without the subject needing to know about these observers explicitly. Here are scenarios in which the Observer pattern is a good fit:
- Event Handling: This pattern is a core concept behind event handling in many frameworks, including .NET. When you have a button and want to execute certain code upon clicking it, you use a variant of the Observer pattern.
- Broadcast Communication: When an object needs to broadcast data to multiple entities but shouldn’t be concerned with who those entities are or what they do. An example might be a logging system that broadcasts log entries to various targets (console, file, remote server).
- Monitoring Systems: Systems that monitor for specific conditions and then trigger alerts. The observers, in this case, are the alerting mechanisms.
- Chat Rooms: Where each user (observer) gets notified when someone posts a message.
- Stock Market Applications: All registered investors (observers) can get notifications when the stock value changes.
- Push Model in Web: Technologies like WebSockets or SignalR where the server pushes updates to client browsers whenever there’s new data.
- Game Programming: Games often involve objects that need to be notified of events, like an achievement system getting notified of game events.
- News Services: A news publisher can notify all subscribers whenever there’s breaking news.
- Middleware Products: Message Queues, Publishers/Subscriber systems, etc., that need to notify consumers of new messages or events.
However, be cautious about:
- Performance Concerns: With many observers or frequent updates, the pattern can introduce performance issues since every change to the subject could lead to a cascade of updates.
- Unexpected Side Effects: If observers have side effects when being updated, these side effects can compound and lead to unexpected behavior.
- Memory Leaks: In languages and environments where memory management is a concern (like in older versions of .NET before garbage collection was improved), care must be taken to deregister observers when they are no longer needed, or they could lead to memory leaks.
So, while the Observer pattern is a powerful tool, it’s crucial to use it judiciously and understand its implications in the context of the broader system.
In the next article, I will discuss the Chain of Responsibility Design Pattern in C# with Examples. In this article, I explain the Real-Time Examples of Observer Design Patterns in C#. I hope you enjoy this Real-Time Examples Observer Design Pattern in C# article.