Menu
CoddyTech

Mẫu Observer (sự kiện)

Thuộc phần Lập trình hướng đối tượng trong hành trình C# của Coddy. Bài học 54 trên 70.

Mẫu Observer thiết lập mối quan hệ một-nhiều, trong đó một đối tượng (đối tượng chủ thể) thông báo cho nhiều đối tượng phụ thuộc (đối tượng quan sát) khi trạng thái của nó thay đổi. Trong C#, sự kiện cung cấp cách tích hợp sẵn để triển khai mẫu này một cách tinh tế.

Mẫu này gồm một bên phát sự kiện và các bên đăng ký phản hồi lại những sự kiện đó. Đây là ví dụ thực tế về một công cụ theo dõi giá cổ phiếu:

public class Stock
{
    private decimal _price;
    
    public event Action<decimal> PriceChanged;
    
    public decimal Price
    {
        get => _price;
        set
        {
            _price = value;
            PriceChanged?.Invoke(_price);  // Thông báo cho tất cả đối tượng theo dõi
        }
    }
}

public class PriceAlert
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Alert: Price is now {newPrice}");
    }
}

public class PriceLogger
{
    public void OnPriceChanged(decimal newPrice)
    {
        Console.WriteLine($"Logged: {newPrice}");
    }
}

Người đăng ký gắn các phương thức (method) của mình vào sự kiện (event) bằng += và có thể gỡ chúng bằng -=:

var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();

stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;

stock.Price = 150.50m;
// Đầu ra:
// Cảnh báo: Giá hiện là 150.50
// Đã ghi nhật ký: 150.50

stock.PriceChanged -= logger.OnPriceChanged;  // Hủy đăng ký
stock.Price = 155.00m;
// Đầu ra: Cảnh báo: Giá hiện là 155.00

Mẫu Observer thúc đẩy sự liên kết lỏng lẻo: lớp Stock không cần biết gì về các observer của nó. Observer mới có thể đăng ký mà không cần sửa đổi publisher, giúp hệ thống dễ mở rộng và bảo trì.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống theo dõi nhiệt độ bằng mẫu Observer với các sự kiện (event). Bạn sẽ tạo một cảm biến thông báo cho nhiều đối tượng quan sát mỗi khi nhiệt độ thay đổi. Cách này rất phù hợp với các tình huống như hệ thống điều hòa khí hậu, nơi các thành phần khác nhau cần phản ứng khi nhiệt độ thay đổi.

Bạn sẽ chia code thành ba tệp:

  • TemperatureSensor.cs: Tạo lớp TemperatureSensor trong không gian tên Monitoring. Đây là bên phát (publisher), tức đối tượng mà các đối tượng khác sẽ quan sát. Cảm biến cần có:
    • Một trường riêng tư để lưu nhiệt độ hiện tại
    • Một sự kiện có tên TemperatureChanged, sử dụng Action<double> để thông báo cho các bên đăng ký về nhiệt độ mới
    • Một thuộc tính Temperature phát sự kiện mỗi khi nhiệt độ được đặt thành giá trị mới
  • Observers.cs: Tạo hai lớp quan sát trong cùng không gian tên. Các lớp này sẽ đăng ký nhận thông báo khi nhiệt độ thay đổi:
    • DisplayUnit, có phương thức OnTemperatureChanged(double temp) để in ra Display: {temp}C
    • AlarmSystem, có phương thức OnTemperatureChanged(double temp) để in ra Alarm Check: {temp}C
  • Program.cs: Kết hợp mọi thứ bằng cách tạo một cảm biến và cả hai đối tượng quan sát. Đăng ký cả hai đối tượng quan sát nhận sự kiện từ cảm biến, rồi cập nhật nhiệt độ. Sau đó, hủy đăng ký hệ thống báo động và cập nhật nhiệt độ lần nữa để cho thấy chỉ bộ hiển thị nhận được thông báo.

Chương trình nhận hai đầu vào:

  • Lần đọc nhiệt độ thứ nhất (ví dụ: 22.5)
  • Lần đọc nhiệt độ thứ hai (ví dụ: 28.0)

Chương trình cần:

  1. Tạo cảm biến và cả hai đối tượng quan sát
  2. Đăng ký cả hai đối tượng quan sát nhận sự kiện TemperatureChanged
  3. Đặt nhiệt độ của cảm biến thành giá trị đầu vào thứ nhất, cả hai đối tượng quan sát đều nhận thông báo
  4. Hủy đăng ký AlarmSystem nhận sự kiện
  5. Đặt nhiệt độ của cảm biến thành giá trị đầu vào thứ hai, chỉ bộ hiển thị nhận thông báo

Ví dụ, nếu đầu vào là 22.5 và 28.0, đầu ra sẽ là:

Display: 22.5C
Alarm Check: 22.5C
Display: 28C

Điều này cho thấy sức mạnh cốt lõi của mẫu Observer: cảm biến không biết và cũng không cần quan tâm ai đang lắng nghe nó. Các đối tượng quan sát có thể đăng ký hoặc hủy đăng ký bất cứ lúc nào, còn bên phát chỉ cần thông báo cho những ai hiện đang lắng nghe!

Tự làm thử

using System;
using Monitoring;

class Program
{
    public static void Main(string[] args)
    {
        // Đọc các giá trị nhiệt độ đầu vào
        double temp1 = Convert.ToDouble(Console.ReadLine());
        double temp2 = Convert.ToDouble(Console.ReadLine());

        // TODO: Tạo một instance của TemperatureSensor

        // TODO: Tạo các instance của DisplayUnit và AlarmSystem

        // TODO: Đăng ký cả hai đối tượng quan sát nhận sự kiện (event) TemperatureChanged

        // TODO: Đặt nhiệt độ của cảm biến thành temp1 (cả hai đối tượng quan sát đều nhận thông báo)

        // TODO: Hủy đăng ký nhận sự kiện của AlarmSystem

        // TODO: Đặt nhiệt độ của cảm biến thành temp2 (chỉ DisplayUnit nhận thông báo)
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C# online