The Solution: The Observer Pattern
To decouple our system, we can use the Observer Pattern. In C#, this is implemented using delegate and event.
The OrderService will act as the Publisher. It doesn't know who will receive the notification; it just announces that "An order has been placed".
The notification services EmailService and SMSService will act as Subscribers.
- The Publisher (OrderService) Here, we define an Action event. Notice how we use a Predicate to evaluate business rules (checking if the order price meets a condition) before firing the event using Invoke function.
public class OrderService
{
public event Action<OrderService> OrderPlaced;
public string Name { get; set; }
public double Price { get; set; }
public OrderService(string name, double price)
{
Name = name;
Price = price;
}
public void ConfirmOrder(Predicate<double> condition)
{
Console.WriteLine($"Order '{Name}' has been successfully placed!");
// Evaluate condition before firing the event
if (condition(Price))
{
OrderPlaced?.Invoke(this);
}
Console.WriteLine("-------------------------------------------------");
}
}
- The Subscribers (Notification Services) These classes are independent of the OrderService. They just need a method whose signature matches the Action delegate.
public class SMSService
{
public void Notification(OrderService order)
{
Console.WriteLine($"[SMS] Sending text message: Your order '{order.Name}' is confirmed!");
}
}
public class EmailService
{
public void Notification(OrderService order)
{
Console.WriteLine($"[Email] Sending email: Thank you for your order '{order.Name}' is confirmed!");
}
}
- Wiring It All Together In my code, I subscribe the services to the OrderPlaced event using the += operator. If I want to remove a service later, we simply use -=.
class Program
{
static void Main(string[] args)
{
OrderService order = new("Exercise Straps", 228);
SMSService sendSMS = new();
EmailService sendEmail = new();
// Subscribe to the event
order.OrderPlaced += sendEmail.Notification;
order.OrderPlaced += sendSMS.Notification;
// Confirm order with a condition (price > 200)
order.ConfirmOrder(x => x > 200);
}
}
Conclusion: Why This Matters
By using the Observer Pattern with C# delegates and events, I transformed a tightly coupled method into a scalable and flexible system.
If i ever need to add a WhatsAppNotificationService, I simply create the class and subscribe it to the OrderPlaced event without touching the main OrderService at all. This is the importance of the Open/Closed Principle!
You can check out the complete, working code for this project on my GitHub repository here:
https://github.com/elshwilahmed/CAT-Task6-Order-Notification-System/tree/main
How do you handle event-driven architectures in your .NET projects? Let me know in the comments!
Top comments (0)