Chuyển tới nội dung chính

Observer Pattern (Mẫu Observer)

Định nghĩa (Definition)

Observer định nghĩa quan hệ one-to-many (Một-Nhiều) giữa các object sao khi một object (subject) thay đổi state, tất cả dependent (observer) của nó được thông báo và cập nhật tự động.

Ví dụ Coffee Shop

Khi khách hàng đặt đơn, nhiều hệ thống cần phản ứng: màn hình bếp (Kitchen Display) hiển thị đơn mới, hệ thống điểm thân thiết (Loyalty System) cộng điểm, kho (Inventory) trừ tồn kho, và khách hàng nhận thông báo. Thay vì đơn hàng biết về mọi hệ thống, nó chỉ cần thông báo cho observer.

Cấu trúc (Structure)

Observer Interface

public record Order(string Customer, string Coffee, CupSize Size, decimal Price);

public interface IOrderObserver
{
void OnOrderPlaced(Order order);
void OnOrderReady(Order order);
}

Subject — Hệ thống đơn hàng (Order System)

public class OrderSubject
{
private readonly List<IOrderObserver> _observers = new();

public void Subscribe(IOrderObserver observer)
{
if (!_observers.Contains(observer))
_observers.Add(observer);
}

public void Unsubscribe(IOrderObserver observer) =>
_observers.Remove(observer);

public void NotifyOrderPlaced(Order order)
{
Console.WriteLine($"\n📦 Đơn mới: {order.Size} {order.Coffee} của {order.Customer}");
foreach (var observer in _observers)
observer.OnOrderPlaced(order);
}

public void NotifyOrderReady(Order order)
{
Console.WriteLine($"\n☕ Đơn sẵn sàng: {order.Size} {order.Coffee} của {order.Customer}");
foreach (var observer in _observers)
observer.OnOrderReady(order);
}
}

Concrete Observer

public class KitchenDisplay : IOrderObserver
{
public void OnOrderPlaced(Order order) =>
Console.WriteLine($" [Bếp] ☕ Đang pha {order.Size} {order.Coffee} cho {order.Customer}");

public void OnOrderReady(Order order) =>
Console.WriteLine($" [Bếp] ✅ {order.Coffee} của {order.Customer} sẵn sàng lấy");
}

public class LoyaltyTracker : IOrderObserver
{
private readonly Dictionary<string, int> _points = new();

public void OnOrderPlaced(Order order)
{
var earned = (int)(order.Price * 10);
_points[order.Customer] = _points.GetValueOrDefault(order.Customer) + earned;
Console.WriteLine($" [Điểm] {order.Customer} nhận +{earned} pts (tổng: {_points[order.Customer]})");
}

public void OnOrderReady(Order order) { } // Không quan tâm event ready
}

public class InventoryTracker : IOrderObserver
{
public void OnOrderPlaced(Order order) =>
Console.WriteLine($" [Kho] Đã trừ nguyên liệu cho {order.Coffee}");

public void OnOrderReady(Order order) { } // Không quan tâm event ready
}

public class CustomerNotifier : IOrderObserver
{
public void OnOrderPlaced(Order order) =>
Console.WriteLine($" [SMS] Chào {order.Customer}, {order.Coffee} của bạn đang được pha!");

public void OnOrderReady(Order order) =>
Console.WriteLine($" [SMS] Chào {order.Customer}, {order.Coffee} của bạn đã sẵn sàng! 🎉");
}

Client

var orderSystem = new OrderSubject();

orderSystem.Subscribe(new KitchenDisplay());
orderSystem.Subscribe(new LoyaltyTracker());
orderSystem.Subscribe(new InventoryTracker());
orderSystem.Subscribe(new CustomerNotifier());

orderSystem.NotifyOrderPlaced(new Order("Alice", "Latte", CupSize.Large, 4.50m));
orderSystem.NotifyOrderReady(new Order("Alice", "Latte", CupSize.Large, 4.50m));
mẹo

OrderSubject không biết observer làm gì — chỉ biết chúng implement IOrderObserver. Thêm observer mới (ví dụ: analytics logger) không cần thay đổi hệ thống đơn hàng (OCP).

.NET tích hợp sẵn: Events

C# có Observer tích hợp trong ngôn ngữ qua event và delegate:

public class CoffeeOrderSystem
{
public event EventHandler<OrderEventArgs>? OrderPlaced;
public event EventHandler<OrderEventArgs>? OrderReady;
}

// Subscribe
system.OrderPlaced += (sender, args) =>
Console.WriteLine($"[Bếp] Đang pha {args.Order.Coffee}");

// Raise
system.OrderPlaced?.Invoke(system, new OrderEventArgs(order));

Sử dụng thực tế trong .NET (.NET Real-World Usage)

  • event / EventHandler — Observer pattern tích hợp trong C#
  • INotifyPropertyChanged — WPF/MAUI data binding
  • IObservable<T> / IObserver<T> — Rx.NET reactive programming
  • SignalR — thông báo real-time đến client đã kết nối
  • IProgress<T> — báo cáo tiến độ không coupling

Khi nào sử dụng (When to Use)

  • Thay đổi một object cần thay đổi các object khác, và không biết chính xác bao nhiêu
  • Object nên thông báo cho object khác mà không tightly coupled
  • Cần kiến trúc event-driven (pub/sub)

Khi nào KHÔNG sử dụng (When NOT to Use)

  • Chỉ có một observer — callback trực tiếp đơn giản hơn
  • Chuỗi update trở nên không thể dự đoán — observer trigger observer gây vòng lặp vô hạn
  • Performance là quan trọng và overhead thông báo ảnh hưởng

Điểm chính (Key Takeaways)

  • Observer thiết lập quan hệ one-to-many với tự động thông báo
  • Subject và observer loosely coupled — subject không biết loại observer
  • C# hỗ trợ tích hợp qua eventIObservable<T>
  • Luôn unsubscribe để tránh memory leak

Câu hỏi phỏng vấn (Interview Questions)

Q: Khác biệt giữa Observer và Pub/Sub là gì? Observer là trực tiếp (Direct) — subject gọi method của observer. Pub/Sub giới thiệu message broker giữa publisher và subscriber, khiến chúng hoàn toàn tách biệt. Observer mặc định synchronous; Pub/Sub thường asynchronous.

Q: Làm sao ngăn memory leak trong Observer? Observer giữ reference đến subject (qua event). Nếu không unsubscribe, GC không thể thu thập. Luôn gọi -= để unsubscribe, hoặc dùng weak reference pattern (WeakEventManager trong WPF).

Q: IObservable<T> khác IEnumerable<T> như thế nào? IEnumerable<T>pull-based — bạn iterate để lấy item. IObservable<T>push-based — item đến khi được tạo. Chúng là đôi của nhau (duality principle trong reactive programming).

  • Mediator — giao tiếp tập trung giữa colleague
  • Strategy — mục đích khác (hoán đổi thuật toán), composition tương tự
  • Delegates & Events — tính năng C# cho Observer
  • SOLID — OCP & DIP — observer mới không làm hỏng code hiện có