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.00Mẫ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ì.
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ớpTemperatureSensortrong không gian tênMonitoring. Đâ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ụngAction<double>để thông báo cho các bên đăng ký về nhiệt độ mới - Một thuộc tính
Temperaturephá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ứcOnTemperatureChanged(double temp)để in raDisplay: {temp}CAlarmSystem, có phương thứcOnTemperatureChanged(double temp)để in raAlarm 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:
- Tạo 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
TemperatureChanged - Đặ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
- Hủy đăng ký
AlarmSystemnhận sự kiện - Đặ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)
}
}
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
1Các khái niệm cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiKhông gian tên và chỉ thịGiới thiệu về lớp và đối tượngTừ khóa thisPhương thức và tham sốTrường và thuộc tínhHàm khởi tạoKhởi tạo đối tượngÔn tập: Máy tính đơn giản4Kế thừa
Cú pháp kế thừa cơ bản (:)Từ khóa 'base'Từ khóa virtual và overrideLớp sealedLớp cơ sở 'object'Ôn tập: hệ phân cấp nhân viên7Tính năng nâng cao
Nạp chồng toán tửBộ lập chỉ mục (this[])Ghi đè ToString()Phương thức mở rộngÔn tập: Danh sách tùy chỉnh10Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếSingleton an toàn với luồngMẫu FactoryMẫu Observer (sự kiện)Mẫu Strategy2Thuộc tính và thành viên tĩnh
Thuộc tính tự độngThuộc tính chỉ đọc hoặc chỉ ghiTrường và phương thức tĩnhLớp tĩnhThành viên có thân biểu thức5Đa hình và interface
Đa hình lúc biên dịch và lúc chạyInterface và lớp trừu tượngNhiều interfaceInterface tường minhUpcasting và downcastingÔn tập: bộ tính toán hình học8Các khái niệm OOP nâng cao
Ưu tiên composition hơn kế thừaGenerics (lớp và phương thức)Delegate và sự kiệnAttribute và reflectionGiao diện IDisposable và câu lệnh usingKiến thức cơ bản về dependency injection11Các mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổ hợpTự luyện tập: Trình biên dịch C# online