Showing posts with label Observer Design Pattern. Show all posts
Showing posts with label Observer Design Pattern. Show all posts

Wednesday, July 29, 2026

Observer Design Pattern


Mastering Design Patterns in C# and ASP.NET Core

Part 4.7 – Observer Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐☆☆ Intermediate
Prerequisites: C#, OOP, Interfaces, Delegates, Events, Dependency Injection, ASP.NET Core


Table of Contents

  1. Introduction

  2. What is the Observer Design Pattern?

  3. Why Do We Need the Observer Pattern?

  4. Real-World Analogy

  5. Subject and Observer Concepts

  6. How the Observer Pattern Works

  7. UML Class Diagram

  8. Components of the Observer Pattern

  9. Complete C# Console Application

  10. Observer Pattern Using C# Events

  11. IObservable<T> and IObserver<T>

  12. ASP.NET Core Implementation

  13. Banking Notification Example

  14. E-Commerce Order Notification Example

  15. Stock Price Monitoring Example

  16. Event-Driven Architecture

  17. Observer vs Pub/Sub

  18. Observer vs Mediator

  19. Observer vs Event-Driven Architecture

  20. Advantages

  21. Disadvantages

  22. Best Practices

  23. Common Mistakes

  24. Real-World Enterprise Scenarios

  25. Interview Questions

  26. Key Takeaways

  27. Conclusion

  28. Coming Up Next


1. Introduction

In real-world applications, one object often needs to notify multiple other objects when something changes.

For example:

Order Status Changed
        |
        +----> Email Notification
        |
        +----> SMS Notification
        |
        +----> Push Notification
        |
        +----> Audit Logging
        |
        +----> Analytics

A common question is:

How can one object notify multiple dependent objects without becoming tightly coupled to them?

The answer is the Observer Design Pattern.

The Observer Pattern is one of the most useful behavioral patterns in modern software development and has strong connections with:

  • C# events

  • Delegates

  • IObservable<T>

  • Reactive programming

  • Notification systems

  • Event-driven applications

  • UI updates

  • Domain events


2. What is the Observer Design Pattern?

The Observer Design Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependent objects are automatically notified.

The object being observed is generally called the:

Subject

The objects receiving notifications are called:

Observers

The basic structure is:

             Subject
                |
       State changes
                |
        +-------+-------+
        |       |       |
        ↓       ↓       ↓
    Observer Observer Observer

When the Subject changes:

Subject
   |
   | Notify()
   |
   +------> Observer 1
   |
   +------> Observer 2
   |
   +------> Observer 3

The Subject does not need to know the detailed implementation of each Observer.


3. Why Do We Need the Observer Pattern?

Consider an e-commerce application.

When an order status changes:

Order #1001
Status = Shipped

Several things may need to happen:

Order Status Changed
       |
       +---- Email Customer
       |
       +---- Send SMS
       |
       +---- Push Notification
       |
       +---- Update Analytics
       |
       +---- Write Audit Log

A poor implementation might look like:

public void UpdateOrderStatus()
{
    SendEmail();

    SendSms();

    SendPushNotification();

    UpdateAnalytics();

    WriteAuditLog();
}

This creates strong coupling.

The OrderService now knows about:

  • Email service

  • SMS service

  • Push notification service

  • Analytics service

  • Audit service

If tomorrow we add:

WhatsApp Notification

the order service must change.

The Observer Pattern allows us to separate these responsibilities.


4. Real-World Analogy

Imagine subscribing to a YouTube channel.

You are an observer.

The YouTube channel is the subject.

When a new video is published:

YouTube Channel
       |
       | New Video
       ↓
Subscribers
       |
       +---- Subscriber A
       +---- Subscriber B
       +---- Subscriber C
       +---- Subscriber D

The channel doesn't need to individually contact every subscriber using custom logic.

Subscribers register themselves.

When something happens, the subscribers receive a notification.

This is the basic idea behind the Observer Pattern.


5. Subject and Observer Concepts

The pattern usually consists of two main abstractions.

Subject

Responsible for:

  • Maintaining observers

  • Adding observers

  • Removing observers

  • Notifying observers

Example:

public interface ISubject
{
    void Attach(IObserver observer);

    void Detach(IObserver observer);

    void Notify();
}

Observer

Responsible for responding to notifications.

public interface IObserver
{
    void Update();
}

The Subject communicates with observers through the abstraction.


6. How the Observer Pattern Works

The lifecycle is:

Step 1 – Create Subject

StockPrice

Step 2 – Create Observers

Mobile App
Web App
Trading Dashboard

Step 3 – Subscribe

StockPrice
    |
    +---- Mobile App
    +---- Web App
    +---- Dashboard

Step 4 – State Changes

Stock Price
$150 → $155

Step 5 – Notify

Notify()

Step 6 – Observers React

Mobile App → Update
Web App → Update
Dashboard → Update

7. UML Class Diagram

A traditional Observer Pattern UML diagram looks like this:

                    +----------------------+
                    |       Subject        |
                    +----------------------+
                    | - observers          |
                    +----------------------+
                    | + Attach()           |
                    | + Detach()           |
                    | + Notify()            |
                    +----------+-----------+
                               |
                               |
                    +----------v-----------+
                    |     IObserver         |
                    +-----------------------+
                    | + Update()            |
                    +----------+------------+
                               |
                 +-------------+-------------+
                 |                           |
        +--------v---------+       +---------v--------+
        | ConcreteObserver |       | ConcreteObserver |
        +------------------+       +------------------+
        | + Update()       |       | + Update()       |
        +------------------+       +------------------+

The key relationship is:

Subject → many Observers

8. Components of the Observer Pattern

8.1 Subject

Maintains the collection of observers.

public interface ISubject
{
    void Attach(IObserver observer);

    void Detach(IObserver observer);

    void Notify();
}

8.2 Concrete Subject

Contains the actual state.

public class Stock : ISubject
{
    private readonly List<IObserver> _observers = new();

    public decimal Price { get; private set; }

    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();
        }
    }

    public void SetPrice(decimal price)
    {
        Price = price;
        Notify();
    }
}

8.3 Observer

public interface IObserver
{
    void Update();
}

8.4 Concrete Observer

public class MobileApp : IObserver
{
    public void Update()
    {
        Console.WriteLine(
            "Mobile App received stock update.");
    }
}

9. Complete C# Console Application

Let's build a complete stock price notification application.

Step 1 – Observer Interface

public interface IObserver
{
    void Update(decimal price);
}

Step 2 – Subject Interface

public interface ISubject
{
    void Attach(IObserver observer);

    void Detach(IObserver observer);

    void Notify();
}

Step 3 – Concrete Subject

public class Stock : ISubject
{
    private readonly List<IObserver> _observers = new();

    public string Symbol { get; }

    public decimal Price { get; private set; }

    public Stock(string symbol)
    {
        Symbol = symbol;
    }

    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(Price);
        }
    }

    public void SetPrice(decimal price)
    {
        Price = price;

        Console.WriteLine(
            $"{Symbol} price changed to {Price:C}");

        Notify();
    }
}

Step 4 – Concrete Observers

Mobile Application

public class MobileApp : IObserver
{
    public void Update(decimal price)
    {
        Console.WriteLine(
            $"Mobile App: Stock price updated to {price:C}");
    }
}

Web Dashboard

public class WebDashboard : IObserver
{
    public void Update(decimal price)
    {
        Console.WriteLine(
            $"Web Dashboard: Stock price updated to {price:C}");
    }
}

Trading System

public class TradingSystem : IObserver
{
    public void Update(decimal price)
    {
        Console.WriteLine(
            $"Trading System: Received price {price:C}");
    }
}

Step 5 – Program

var stock = new Stock("MSFT");

var mobileApp = new MobileApp();
var webDashboard = new WebDashboard();
var tradingSystem = new TradingSystem();

stock.Attach(mobileApp);
stock.Attach(webDashboard);
stock.Attach(tradingSystem);

stock.SetPrice(150);

Console.WriteLine();

stock.SetPrice(155);

Console.WriteLine();

stock.Detach(webDashboard);

stock.SetPrice(160);

Possible output:

MSFT price changed to $150.00
Mobile App: Stock price updated to $150.00
Web Dashboard: Stock price updated to $150.00
Trading System: Received price $150.00

MSFT price changed to $155.00
Mobile App: Stock price updated to $155.00
Web Dashboard: Stock price updated to $155.00
Trading System: Received price $155.00

MSFT price changed to $160.00
Mobile App: Stock price updated to $160.00
Trading System: Received price $160.00

Notice what happened:

WebDashboard
     |
     ↓
Detach()

It no longer receives notifications.


10. Observer Pattern Using C# Events

In modern C#, we don't always need to manually maintain:

List<IObserver>

C# provides:

  • Delegates

  • Events

These are frequently used to implement observer-like behavior.

For example:

public class Stock
{
    public event EventHandler<StockPriceChangedEventArgs>?
        PriceChanged;

    public decimal Price { get; private set; }

    public void SetPrice(decimal price)
    {
        Price = price;

        PriceChanged?.Invoke(
            this,
            new StockPriceChangedEventArgs(price));
    }
}

Event arguments:

public class StockPriceChangedEventArgs
    : EventArgs
{
    public decimal Price { get; }

    public StockPriceChangedEventArgs(
        decimal price)
    {
        Price = price;
    }
}

Subscribe:

stock.PriceChanged +=
    OnStockPriceChanged;

Handler:

void OnStockPriceChanged(
    object? sender,
    StockPriceChangedEventArgs e)
{
    Console.WriteLine(
        $"Price changed to {e.Price:C}");
}

Unsubscribe:

stock.PriceChanged -=
    OnStockPriceChanged;

This is a very common implementation of observer-style notification in C#.


11. How C# Events Work

The relationship is:

Publisher
    |
    | event
    ↓
Delegate Invocation List
    |
    +---- Subscriber 1
    +---- Subscriber 2
    +---- Subscriber 3

When:

PriceChanged?.Invoke(...);

is executed, subscribed handlers are called.

Therefore:

Event
 ↓
Delegate
 ↓
Multiple Subscribers

This is why understanding the Observer Pattern helps developers understand C# events.


12. IObservable<T> and IObserver<T>

.NET also provides explicit observer abstractions:

IObservable<T>
IObserver<T>

The IObservable<T> represents the producer.

The IObserver<T> represents the subscriber.

The observer interface provides:

void OnNext(T value);

void OnError(Exception error);

void OnCompleted();

For example:

public class StockObserver
    : IObserver<decimal>
{
    public void OnNext(decimal value)
    {
        Console.WriteLine(
            $"Price received: {value:C}");
    }

    public void OnError(Exception error)
    {
        Console.WriteLine(
            $"Error: {error.Message}");
    }

    public void OnCompleted()
    {
        Console.WriteLine(
            "Stock stream completed.");
    }
}

This model is especially useful when working with streams of data and reactive programming concepts.


13. ASP.NET Core Implementation

Let's create a realistic notification example.

Suppose an order changes status:

Order
  ↓
Status Changed
  ↓
Notification Subscribers

We might have:

Order Service
     |
     +---- Email
     |
     +---- SMS
     |
     +---- Push Notification
     |
     +---- Audit Log

14. Order Event

Create an event model:

public record OrderStatusChangedEvent(
    int OrderId,
    string Status);

15. Observer Interface

public interface IOrderObserver
{
    Task UpdateAsync(
        OrderStatusChangedEvent orderEvent);
}

16. Email Observer

public class EmailNotificationObserver
    : IOrderObserver
{
    public Task UpdateAsync(
        OrderStatusChangedEvent orderEvent)
    {
        Console.WriteLine(
            $"Email sent for Order {orderEvent.OrderId}");

        return Task.CompletedTask;
    }
}

17. SMS Observer

public class SmsNotificationObserver
    : IOrderObserver
{
    public Task UpdateAsync(
        OrderStatusChangedEvent orderEvent)
    {
        Console.WriteLine(
            $"SMS sent for Order {orderEvent.OrderId}");

        return Task.CompletedTask;
    }
}

18. Audit Observer

public class AuditObserver
    : IOrderObserver
{
    public Task UpdateAsync(
        OrderStatusChangedEvent orderEvent)
    {
        Console.WriteLine(
            $"Audit entry created for Order " +
            $"{orderEvent.OrderId}");

        return Task.CompletedTask;
    }
}

19. Order Notification Service

public interface IOrderNotificationService
{
    Task NotifyAsync(
        OrderStatusChangedEvent orderEvent);
}

Implementation:

public class OrderNotificationService
    : IOrderNotificationService
{
    private readonly IEnumerable<IOrderObserver>
        _observers;

    public OrderNotificationService(
        IEnumerable<IOrderObserver> observers)
    {
        _observers = observers;
    }

    public async Task NotifyAsync(
        OrderStatusChangedEvent orderEvent)
    {
        foreach (var observer in _observers)
        {
            await observer.UpdateAsync(orderEvent);
        }
    }
}

This is a powerful ASP.NET Core technique.

The service doesn't need to know:

Email
SMS
Audit
Push

It simply receives all registered observers.


20. Register Observers with Dependency Injection

In Program.cs:

builder.Services.AddScoped<
    IOrderObserver,
    EmailNotificationObserver>();

builder.Services.AddScoped<
    IOrderObserver,
    SmsNotificationObserver>();

builder.Services.AddScoped<
    IOrderObserver,
    AuditObserver>();

builder.Services.AddScoped<
    IOrderNotificationService,
    OrderNotificationService>();

When we inject:

IEnumerable<IOrderObserver>

ASP.NET Core provides all registered implementations.

Therefore:

IEnumerable<IOrderObserver>
          |
          +---- EmailNotificationObserver
          +---- SmsNotificationObserver
          +---- AuditObserver

This is an excellent example of combining:

  • Observer Pattern

  • Dependency Injection

  • Open/Closed Principle


21. ASP.NET Core Controller

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly IOrderNotificationService
        _notificationService;

    public OrdersController(
        IOrderNotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    [HttpPost("{id}/ship")]
    public async Task<IActionResult> ShipOrder(
        int id)
    {
        var orderEvent =
            new OrderStatusChangedEvent(
                id,
                "Shipped");

        await _notificationService
            .NotifyAsync(orderEvent);

        return Ok(new
        {
            OrderId = id,
            Status = "Shipped"
        });
    }
}

The controller doesn't know about:

Email
SMS
Audit

It only interacts with the notification abstraction.


22. Banking Example

Consider a banking application.

A transaction occurs:

$500 deposited

Several components may need to react:

Bank Transaction
       |
       +---- Account Balance
       |
       +---- Email Notification
       |
       +---- SMS Notification
       |
       +---- Fraud Detection
       |
       +---- Audit Log
       |
       +---- Analytics

Instead of putting all logic into the transaction service, observers can subscribe to transaction events.

For example:

public record TransactionCompletedEvent(
    string AccountNumber,
    decimal Amount,
    string TransactionType);

Observers:

EmailObserver
SmsObserver
FraudDetectionObserver
AuditObserver
AnalyticsObserver

This makes the system easier to extend.


23. E-Commerce Order Example

Suppose:

Order #1001
Status = Delivered

Observers might include:

CustomerNotificationObserver
InventoryObserver
AnalyticsObserver
AuditObserver
LoyaltyPointsObserver

Flow:

                 Order Service
                      |
                 Order Delivered
                      |
          +-----------+-----------+
          |           |           |
          ↓           ↓           ↓
      Customer     Inventory   Analytics
      Notify       Update      Update

Adding another subscriber does not necessarily require changing the core order logic.


24. Stock Price Monitoring

Another common example is financial market monitoring.

Stock Price Service
       |
       | Price Changed
       |
       +---- Trading Dashboard
       |
       +---- Mobile Application
       |
       +---- Alert Service
       |
       +---- Analytics

If the price becomes:

$150 → $155

all subscribers can receive the update.


25. Event-Driven Architecture

Observer Pattern concepts are closely related to event-driven architecture, but they are not identical.

A simple in-process observer system:

Object
 ↓
Event
 ↓
Observers

An enterprise event-driven architecture may look like:

Order Service
      |
      | OrderCreated
      ↓
Message Broker
      |
      +---- Notification Service
      |
      +---- Inventory Service
      |
      +---- Analytics Service
      |
      +---- Shipping Service

The important difference is that enterprise systems often use:

  • Message brokers

  • Queues

  • Topics

  • Durable messages

  • Retry policies

  • Dead-letter queues

  • Distributed consumers

Examples include Azure Service Bus, RabbitMQ, Kafka, and other messaging technologies.

The Observer Pattern provides the conceptual foundation for one-to-many notification, while distributed messaging provides a different architectural mechanism for implementing event-driven systems.


26. Observer vs Pub/Sub

These concepts are related but not exactly the same.

ObserverPub/Sub
Subject knows observersPublisher generally doesn't know subscribers
Often in-processCommonly distributed
Direct notificationUsually broker/topic based
Simple communicationSupports larger distributed systems
Often synchronousCan be asynchronous

Observer:

Subject
   |
   +---- Observer A
   +---- Observer B

Pub/Sub:

Publisher
    |
    ↓
Message Broker
    |
    +---- Subscriber A
    +---- Subscriber B

27. Observer vs Mediator

These patterns can look similar.

Observer

Focuses on:

One-to-many notification

Example:

Order
 |
 +---- Email
 +---- SMS
 +---- Audit

Mediator

Focuses on:

Centralized communication between multiple components

Example:

Component A
     |
Component B → Mediator ← Component C
     |
Component D

The Observer pattern is primarily about notification.

Mediator is primarily about coordinating communication.


28. Observer vs Event-Driven Architecture

Observer:

Subject
   ↓
Observers

Event-driven architecture:

Producer
   ↓
Event Broker
   ↓
Consumers

Observer is often an object-level design pattern.

Event-driven architecture is an architectural approach.


29. Advantages of Observer Pattern

1. Loose Coupling

The Subject depends on an abstraction instead of concrete observers.


2. Open/Closed Principle

New observers can often be added without modifying the Subject.

Existing Subject
       +
New Observer

3. Supports One-to-Many Communication

One state change can notify many components.


4. Extensibility

Adding:

Email
SMS
Push
Audit
Analytics

can be straightforward.


5. Separation of Responsibilities

The Subject handles state.

Observers handle their respective reactions.


6. Natural Fit for Notifications

Examples:

Order Status
Stock Price
Bank Transaction
System Alerts

30. Disadvantages of Observer Pattern

1. Too Many Observers

If hundreds of observers are registered, a single notification can trigger substantial work.


2. Notification Order

Unless explicitly designed, observers may not have a guaranteed execution order.


3. Hidden Dependencies

It may not be obvious from reading the Subject that multiple observers will execute when an event occurs.


4. Error Handling

One observer throwing an exception can potentially affect other observers, depending on how notification is implemented.


5. Memory Leaks with Events

Long-lived publishers holding references to subscribers can prevent subscribers from being garbage collected.

This is particularly important in long-running applications.


6. Synchronous Processing Can Become Slow

If every observer executes synchronously:

Subject
 ↓
Observer 1 → 100 ms
 ↓
Observer 2 → 500 ms
 ↓
Observer 3 → 300 ms

the total operation can become slow.

For distributed or long-running work, asynchronous messaging or background processing may be more appropriate.


31. Best Practices

1. Depend on Abstractions

Use:

IOrderObserver

instead of concrete implementations.


2. Keep Observers Focused

An observer should ideally have one clear responsibility.

For example:

EmailObserver

should focus on email notification.


3. Avoid Heavy Synchronous Work

Don't perform long-running operations inside a request thread unnecessarily.

Consider:

Event
 ↓
Queue
 ↓
Background Worker

when appropriate.


4. Handle Observer Failures Carefully

Don't allow one optional observer to unintentionally prevent all other observers from running.


5. Unsubscribe When Necessary

For event-based systems:

publisher.Event -= Handler;

is important when the subscriber's lifetime is shorter than the publisher's lifetime.


6. Use Immutable Event Data

Records are convenient:

public record OrderStatusChangedEvent(
    int OrderId,
    string Status);

The event payload should generally represent a stable fact.


7. Don't Use Observer Everywhere

If a simple direct method call is sufficient, introducing Observer may add unnecessary complexity.


32. Common Mistakes

Mistake 1 – Strong Coupling

Avoid:

public class OrderService
{
    private readonly EmailService _email;
    private readonly SmsService _sms;
    private readonly AuditService _audit;
}

when the business requirement is better represented as extensible notification subscribers.


Mistake 2 – Too Much Logic in the Subject

The Subject should not perform every observer's business responsibility.


Mistake 3 – Forgetting to Unsubscribe

With event-based subscriptions, improper lifetime management can create memory retention problems.


Mistake 4 – Ignoring Exceptions

If multiple observers are notified synchronously, determine what should happen when one fails.


Mistake 5 – Confusing Observer with Distributed Messaging

An in-memory event does not automatically provide:

  • Durability

  • Retry

  • Cross-service communication

  • Guaranteed delivery

  • Dead-letter handling

Those requirements typically need messaging infrastructure.


Mistake 6 – Assuming Notification Order

Don't rely on a specific observer execution order unless the application explicitly guarantees it.


33. Real-World Enterprise Scenarios

The Observer Pattern can be useful in:

Banking

Transaction
 ↓
Notifications
Fraud Detection
Audit
Analytics

E-Commerce

Order Created
 ↓
Inventory
Payment
Notification
Shipping
Analytics

Monitoring

System Alert
 ↓
Email
SMS
Dashboard
Logging

Stock Market Applications

Price Changed
 ↓
Mobile
Web
Trading
Alerts

User Management

User Registered
 ↓
Welcome Email
Audit
Analytics
Profile Initialization

Workflow Systems

Workflow State Changed
 ↓
Notification
Audit
Task Assignment
Analytics

34. Observer Pattern and SOLID

Observer works particularly well with several SOLID principles.

Single Responsibility Principle

Each observer handles one responsibility.

EmailObserver → Email
SmsObserver → SMS
AuditObserver → Audit

Open/Closed Principle

New observers can be introduced without modifying the existing Subject.


Dependency Inversion Principle

The Subject depends on:

IObserver

instead of:

ConcreteObserver

35. Observer Pattern and Dependency Injection

ASP.NET Core makes Observer implementations particularly clean with:

IEnumerable<IOrderObserver>

For example:

public OrderNotificationService(
    IEnumerable<IOrderObserver> observers)
{
    _observers = observers;
}

The service automatically receives all registered implementations.

This means the architecture can evolve from:

Email

to:

Email
SMS
Push
Audit
Analytics

without changing the notification service's core structure.


36. Observer Pattern and Asynchronous Processing

Suppose an order API receives:

POST /api/orders

If the application synchronously performs:

Email
SMS
Push
Analytics
Audit

the API response may become slower.

An alternative is:

API
 ↓
Order Created Event
 ↓
Message Queue
 ↓
Consumers

For example:

                Order Service
                     |
                     ↓
              OrderCreated
                     |
                     ↓
                Message Broker
            /        |        \
           /         |         \
          ↓          ↓          ↓
       Email      Inventory   Analytics
       Service     Service     Service

This is no longer simply an in-process Observer Pattern; it becomes distributed event-driven architecture.


37. Observer Pattern in Modern .NET

There are several ways to implement Observer-like behavior in .NET:

Option 1 – Interfaces

IObserver

Useful for explicit pattern implementation.

Option 2 – Events

event EventHandler

Very common for in-process notifications.

Option 3 – IObservable<T>

Useful for observable streams.

Option 4 – Application Events

Useful for decoupling application components.

Option 5 – Domain Events

Useful for business events inside domain-driven applications.

Option 6 – Message Brokers

Useful for distributed event-driven systems.


38. Interview Questions

Beginner

1. What is the Observer Design Pattern?

The Observer Pattern defines a one-to-many dependency where observers are notified when the subject changes.


2. What are the main components?

Subject
Observer
Concrete Subject
Concrete Observer

3. What is a Subject?

The object whose state changes and which notifies observers.


4. What is an Observer?

An object that receives notifications and reacts to changes.


5. What problem does Observer solve?

It reduces coupling between an object that generates changes and objects that need to react to those changes.


Intermediate

6. How is Observer implemented in C#?

Common approaches include:

Interfaces
Delegates
Events
IObservable<T>

7. How do C# events relate to Observer?

C# events provide a built-in mechanism for one-to-many notification and are frequently used to implement Observer-style behavior.


8. What is IObservable<T>?

It represents a producer of observable values that can be subscribed to by implementations of IObserver<T>.


9. What are the methods in IObserver<T>?

OnNext()
OnError()
OnCompleted()

10. What is the difference between Observer and Pub/Sub?

Observer usually represents direct object-level notification, while Pub/Sub commonly introduces an intermediary such as a message broker.


39. Advanced Interview Questions

11. What is the difference between Observer and Mediator?

Observer focuses on one-to-many notification.

Mediator centralizes communication and coordination between multiple components.


12. Can Observer be asynchronous?

Yes.

Observers can expose:

Task UpdateAsync(...)

or an asynchronous event-processing architecture can be introduced.


13. What happens if one observer throws an exception?

It depends on the implementation.

A robust notification system should define whether:

  • Processing stops

  • Other observers continue

  • Errors are logged

  • Failed work is retried


14. Can Observer cause memory leaks?

Yes, especially with events.

A long-lived publisher can keep references to subscribers that should otherwise be eligible for garbage collection.


15. How can you avoid event-related memory leaks?

Unsubscribe appropriately:

publisher.Event -= Handler;

Also consider subscription lifetime and ownership carefully.


16. How would you implement Observer in ASP.NET Core?

One approach is:

IObserver
     ↓
Multiple Implementations
     ↓
Dependency Injection
     ↓
IEnumerable<IObserver>

17. Is Observer suitable for microservices?

The Observer concept can inspire event-driven communication, but direct in-memory Observer implementations generally do not provide reliable cross-service communication.

For microservices, consider:

  • Message brokers

  • Events

  • Queues

  • Topics

  • Durable messaging


18. What is a Domain Event?

A domain event represents something meaningful that happened within the business domain.

For example:

OrderPlaced
PaymentCompleted
OrderShipped
CustomerRegistered

Observers or event handlers can react to those events.


19. What is the difference between an Event and an Event Handler?

The event represents something that happened.

The event handler contains the logic that reacts to that event.

Event
 ↓
Handler

20. When should you avoid Observer?

Avoid it when:

  • There are only two tightly related components.

  • A direct method call is simpler.

  • Notification behavior is difficult to understand.

  • The observer chain becomes excessively complicated.

  • A distributed messaging solution is actually required.


40. Practical Enterprise Architecture

A modern .NET application might evolve like this:

                   Order Service
                        |
                        ↓
                 OrderPlaced Event
                        |
            +-----------+-----------+
            |           |           |
            ↓           ↓           ↓
         Email       Audit       Analytics
       Observer     Observer      Observer

For a distributed architecture:

                   Order Service
                        |
                        ↓
                  OrderPlaced
                        |
                        ↓
                 Message Broker
               /        |        \
              /         |         \
             ↓          ↓          ↓
        Notification  Inventory  Analytics
           Service      Service     Service

The first is an in-process Observer-style design.

The second is an event-driven distributed architecture.


41. Observer Pattern – Complete Conceptual Flow

The complete lifecycle is:

1. Create Subject
        ↓
2. Create Observers
        ↓
3. Attach / Subscribe
        ↓
4. Subject State Changes
        ↓
5. Subject Raises Notification
        ↓
6. Observers Receive Notification
        ↓
7. Each Observer Performs Its Responsibility
        ↓
8. Observer Can Unsubscribe

In code:

subject.Attach(observer);

subject.ChangeState();

subject.Notify();

subject.Detach(observer);

Or with C# events:

publisher.Event += Handler;

publisher.ChangeState();

publisher.Event -= Handler;

42. Key Takeaways

Remember these important points:

1. Observer is a Behavioral Design Pattern

It focuses on communication between objects.

2. It represents a one-to-many relationship

One Subject
     ↓
Many Observers

3. It reduces coupling

The Subject does not need to know the concrete implementation of observers.

4. C# events are closely related to Observer

event EventHandler

is a common way to implement one-to-many notification.

5. IObservable<T> is another .NET mechanism

It provides an explicit observable/subscriber model.

6. Dependency Injection works very well with Observer

ASP.NET Core can inject:

IEnumerable<IOrderObserver>

to provide multiple observers.

7. Observer is not the same as Pub/Sub

Observer is usually direct object-level notification.

Pub/Sub typically uses an intermediary.

8. Observer is not the same as distributed event-driven architecture

For microservices, reliable communication usually requires messaging infrastructure.

9. Watch observer lifetime

Especially when using C# events.

10. Keep observers focused

Each observer should ideally have a clear responsibility.


Conclusion

The Observer Design Pattern is one of the most practical behavioral patterns for modern software development.

It provides a clean way to establish:

One-to-Many Communication

without tightly coupling the publisher to every consumer.

The basic architecture is:

                    Subject
                       |
                  State Changed
                       |
              +--------+--------+
              |        |        |
              ↓        ↓        ↓
           Observer Observer Observer

In C#, this concept appears naturally through:

Interfaces
Delegates
Events
IObservable<T>

In ASP.NET Core applications, it can be combined with:

Dependency Injection
Domain Events
Application Events
Notification Handlers

And in distributed systems, the same fundamental one-to-many notification concept can evolve into:

Producer
   ↓
Event / Message
   ↓
Message Broker
   ↓
Multiple Consumers

The most important lesson is:

Use the Observer Pattern when multiple components need to react to a change without making the component that produces the change tightly coupled to every consumer.

It is particularly valuable for notification systems, order processing, stock monitoring, banking events, UI updates, audit logging, analytics, and event-driven application designs.


🚀 Coming Up Next: Part 4.8 – State Design Pattern

In the next article, we'll explore the State Design Pattern, including:

  • What is the State Pattern?

  • Why do we need it?

  • State-dependent behavior

  • Eliminating large if-else and switch statements

  • Context and State concepts

  • UML Class Diagram

  • Complete C# Console Application

  • Order processing example

  • Banking account state example

  • ASP.NET Core implementation

  • State transitions

  • State machines

  • Real-world enterprise examples

  • State vs Strategy

  • State vs Command

  • State vs Chain of Responsibility

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

The State Pattern is particularly useful in enterprise applications where an object's behavior changes significantly based on its current state—for example, Order Pending → Confirmed → Shipped → Delivered → Cancelled.

Don't Copy

Protected by Copyscape Online Plagiarism Checker