Understanding the Observer Pattern
The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. In simpler terms, when one object (the subject) changes its state, all its dependents (observers) are notified and updated automatically. This pattern promotes a loosely coupled architecture, fostering scalability and ease of maintenance.
The Anatomy of Observer Pattern
- Subject:
- The entity being observed, often encapsulating a state or data of interest.
- Maintains a list of observers and notifies them of any state changes.
- Observer:
- The interface or abstract class that defines the update method.
- Concrete observer classes implement this method to respond to state changes.
Concrete Case: Real-time Stock Market Updates
Let’s delve into a practical example to grasp the Observer Pattern’s implementation. Consider a real-time stock market monitoring system, where multiple investors want to stay informed about the latest changes in the stock prices.
The subject : StockMarket
public class StockMarket
{
private double _currentPrice;
private List<IInvestor> _investors = new List<IInvestor>();
public double CurrentPrice
{
get { return _currentPrice; }
set
{
_currentPrice = value;
NotifyInvestors();
}
}
public void AddInvestor(IInvestor investor)
{
_investors.Add(investor);
}
public void RemoveInvestor(IInvestor investor)
{
_investors.Remove(investor);
}
private void NotifyInvestors()
{
foreach (var investor in _investors)
{
investor.Update(_currentPrice);
}
}
}
The observer : StockInvestor
// Observer
public interface IInvestor
{
void Update(double stockPrice);
}
// Concrete Observer
public class StockInvestor : IInvestor
{
private string _name;
public StockInvestor(string name)
{
_name = name;
}
public void Update(double stockPrice)
{
Console.WriteLine($"{_name} received an update: Stock price is {stockPrice}");
}
}
Let’s simulate the behavior.
class Program
{
static void Main(string[] args)
{
var stockMarket = new StockMarket();
var investor1 = new StockInvestor("John");
var investor2 = new StockInvestor("Alice");
stockMarket.AddInvestor(investor1);
stockMarket.AddInvestor(investor2);
// Simulate stock price changes
stockMarket.CurrentPrice = 150.0;
stockMarket.CurrentPrice = 155.5;
// Remove an investor
stockMarket.RemoveInvestor(investor2);
// Simulate more changes
stockMarket.CurrentPrice = 152.8;
}
}
The console output :
Using event-based approach
To improve the given code using events, we can introduce an event in the StockMarket
class that investors can subscribe to. This approach allows the StockMarket
to notify its investors about price changes without explicitly calling their Update
method. Here’s how you can modify the code:
Step 1: Define an Event in StockMarket
First, define an event in the StockMarket
class that will be triggered whenever the stock price changes. This event will be of type Action<double>
, where the action takes a double
parameter representing the new stock price.
public class StockMarket
{
private double _currentPrice;
private List<IInvestor> _investors = new List<IInvestor>();
public event Action<double> PriceChanged; // New event
public double CurrentPrice
{
get { return _currentPrice; }
set
{
if (_currentPrice!= value)
{
_currentPrice = value;
OnPriceChanged(value); // Trigger the event
}
}
}
public void AddInvestor(IInvestor investor)
{
_investors.Add(investor);
}
public void RemoveInvestor(IInvestor investor)
{
_investors.Remove(investor);
}
protected virtual void OnPriceChanged(double newPrice) // Method to trigger the event
{
PriceChanged?.Invoke(newPrice);
}
}
Step 2: Modify IInvestor
Interface
Since we’re moving away from the direct Update
method call, the IInvestor
interface no longer needs to have an Update
method. However, we’ll keep it for backward compatibility if needed. We will replace it by the SubscribeToPriceChanges
method.
public interface IInvestor
{
void SubscribeToPriceChanges(StockMarket market);
}
Step 3: Implement the Event Subscription in StockInvestor
Now, each investor can subscribe to the PriceChanged
event of the StockMarket
. This way, they will be notified automatically whenever the stock price changes.
public class StockInvestor : IInvestor
{
private string _name;
public StockInvestor(string name)
{
_name = name;
}
public void SubscribeToPriceChanges(StockMarket market)
{
market.PriceChanged += (newPrice) => Console.WriteLine($"{_name} received an update: Stock price is {newPrice}");
}
}
Step 4: Update the Main
Method
Finally, update the Main
method to subscribe the investors to the PriceChanged
event.
var stockMarket = new StockMarket();
var investor1 = new StockInvestor("John");
var investor2 = new StockInvestor("Alice");
stockMarket.AddInvestor(investor1);
stockMarket.AddInvestor(investor2);
// Subscribe investors to price changes
investor1.SubscribeToPriceChanges(stockMarket);
investor2.SubscribeToPriceChanges(stockMarket);
// Simulate stock price changes
stockMarket.CurrentPrice = 150.0;
stockMarket.CurrentPrice = 155.5;
// Remove an investor
stockMarket.RemoveInvestor(investor2);
// Simulate more changes
stockMarket.CurrentPrice = 152.8;
This refactoring introduces the event-based approach, allowing for a cleaner separation of concerns between the StockMarket
and its investors. Investors now subscribe to receive notifications about price changes, making the system more flexible and easier to extend.
Conclusion
The Observer Pattern empowers developers to create systems where objects communicate seamlessly without tight coupling. By employing this pattern, we enhance the extensibility and maintainability of our code, ensuring that changes in one part of the system do not lead to a cascade of modifications elsewhere.
Introducing the event-based approach significantly improves the design of the StockMarket
system by enhancing modularity, flexibility, scalability, and ease of extension. It also simplifies testing by clearly separating the responsibilities of different components.
Laisser un commentaire