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!

