Ä Hiện
thực đối số sự kiện tùy biến
Í Khi dựng lên một sự kiện, bạn cần truyền một trạng thái đặc-trưng-sự-kiện
cho các phương thức thụ lý sự kiện.
▪ Tạo một lớp đối số sự kiện tùy biến dẫn xuất từ lớp System.EventArg. Khi
dựng lên sự kiện, hãy tạo một thể hiện của lớp đối số sự kiện và truyền nó cho
các phương thức thụ lý s
ự kiện.
Khi khai báo các kiểu sự kiện, thông thường bạn sẽ cần truyền trạng thái đặc-trưng-sự-
kiện cho các phương thức thụ lý sự kiện. Để tạo một lớp đối số sự kiện tùy biến tuân theo
mẫu Event do .NET Framework định nghĩa, bạn cần:
1. Dẫn xuất lớp đối số sự kiện tùy biến từ lớp EventArgs. Lớp EventArgs không chứa
dữ liệuvà được sử dụng cùng với các sự kiện không cần truyền trạng thái.
2. Đặt một tên có ý nghĩa cho lớp đối số sự kiện tùy biến, kết thúc bằng từ EventArgs;
chẳng hạn, DiskFullEventArgs hay MailReceivedEventArgs.
3. Đánh dấu lớp đối số sự kiện là sealed nếu bạn không muốn các lớp đối số sự kiện
khác có thể thừa kế nó.
4. Hiện thực thêm các thuộc tính vàcác thành viên dữ liệu để hỗ trợ trạng thái sự kiện
mà bạn cần truyền cho các phương thức thụ lý sự kiện. Tốt nhất là làm cho trạng
thái sự kiện trở nên bất biến (immutable), như vậy bạn nên sử dụng các thành viên
dữ liệu private readonly và sử dụng các thuộc tính public để cho phép truy xuất chỉ-
đọc đến các thành viên dữ liệu này.
5. Hiện thực một phương thức khởi dựng public hỗ trợ cấu hình ban đầu của trạng thái
sự kiện.
6. Làm cho lớp đối số sự kiện của bạn trở nên khả-tuần-tự-hóa (serializable) để bộ
thực thi có thể marshal các thể hiện của nó qua các biên miền ứng dụng và biên
máy. Áp dụng đặc tính System.SerializableAttribute thường là đã đủ cho các lớp đối
số sự kiện. Tuy nhiên, nếu lớp đối số sự kiện có các yêu cầu tuần tự hóa đặc biệt,
bạn phải hiện thực giaodiện System.Runtime.Serialization.ISerializable (xem m
ục
16.1 để biết cách làm cho một lớp trở nên khả-tuần-tự-hóa).
Đoạn mã dưới đây trình bày một lớp đối số sự kiện tùy biến có tên là
MailReceivedEventArgs. Giả sử có một mail-server truyền các thể hiện của lớp
MailReceivedEventArgs cho các phương thức thụ lý sự kiện nhận một thông điệp e-mail.
Lớp này chứa các thông tin về người gửi và chủ đề của thông điệp e-mail.
using System;
[Serializable]
public sealed class MailReceivedEventArgs : EventArgs {
// Các thành viên private readonly giữ trạng thái sự kiện
// (được phân bổ cho tất cả các phương thức thụ lý sự kiện).
// Lớp MailReceivedEventArgs sẽ cho biết ai đã gửi mail
// và chủ đề là gì.
private readonly string from;
private readonly string subject;
// Phương thức khởi dựng (khởi tạo trạng thái sự kiện).
public MailReceivedEventArgs(string from, string subject) {
this.from = from;
this.subject = subject;
}
// Các thuộc tính chỉ-đọc cho phép truy xuất
// trạng thái sự kiện.
public string From { get { return from; } }
public string Subject { get { return subject; } }
}
Ä Hiện
thực mẫu Singleton
Í Bạn cần bảo đảm chỉ có một thể hiện của một kiểu tồn tại ở một thời điểm cho
trước và thể hiện đó là khả-truy-xuất đối với tất cả các phần tử của ứng dụng.
▪ Hiện thực kiểu này theo mẫu Singleton như sau:
7. Hiện thực một thành viên tĩnh private để giữ một tham chiếu đến thể hiện
của kiểu.
8. Hiện thực một thuộc tính tĩnh khả-truy-xuất-công-khai để cho phép truy
xuất chỉ-đọc đến thể hiện.
9. Hiện thực một phương thức khởi dựng private để mã lệnh không thể tạo
thêm các thể hiện của kiểu.
Trong tất cả cácmẫu được biết đến, có lẽ mẫu Singleton được biết đến nhiều nhất và
thường được sử dụng nhất. Mục đích của mẫu Singleton là bảo đảm chỉ có một thể hiện
của một kiểu t
ồn tại ở một thời điểm cho trước và cho phép truy xuất toàn cục đến các
chức năng của thể hiện đó. Đoạn mã dưới đây trình bày một hiện thực của mẫu Singleton
cho một lớp có tên là SingletonExample:
public class SingletonExample {
// Thành viên tĩnh dùng để giữ một tham chiếu đến thể hiện singleton.
private static SingletonExample instance;
// Phương thức khởi dựng tĩnh dùng để tạo thể hiện singleton.
// Một cách khác là sử dụng "Lazy Initialization" trong
// thuộc tính Instance.
static SingletonExample () {
instance = new SingletonExample();
}
// Phương thức khởi dựng private dùng để ngăn mã lệnh tạo thêm
// các thể hiện của kiểu singleton.
private SingletonExample () {}
// Thuộc tính public cho phép truy xuất đến thể hiện singleton.
public static SingletonExample Instance {
get { return instance; }
}
// Các phương thức public cung cấp chức năng của singleton.
public void SomeMethod1 () { /* */ }
public void SomeMethod2 () { /* */ }
}
Để gọi các chức năng của lớp SingletonExample, bạn có thể lấy về một tham chiếu đến
singleton bằng thuộc tính Instance và rồi gọi các phương thức của nó. Bạn cũng có thể
trực tiếp thực thi các thành viên của singleton thông qua thuộc tính Instance. Đoạn mã
dưới đây trình bày cả hai cách này:
// Thu lấy tham chiếu đến singleton và gọi một phương thức của nó.
SingletonExample s = SingletonExample.Instance;
s.SomeMethod1();
// Thực thi chức năng của singleton mà không cần tham chiếu.
SingletonExample.Instance.SomeMethod2();
Ä Hiện
thực mẫu Observer
Í Bạn cần hiện thực một cơ chế hiệu quả để một đối tượng (subject) báo với các
đối tượng khác (observer) về những thay đổi trạng thái của nó.
▪ Hiện thực mẫu Observer bằng các kiểu ủy nhiệm (đóng vai trò là các con trỏ
hàm an-toàn-về-kiểu-dữ-liệu—type-safe function pointer) vàcác kiểu sự kiện.
Cách truyền thống khi hiện thực mẫu Observer là hiệ
n thực hai giao diện: một để mô tả
observer (IObserver) và một để mô tả subject (ISubject). Các đối tượng có hiện thực
IObserver sẽ đăng ký với subject, cho biết chúng muốn được thông báo về các sự kiện
quan trọng (như thay đổi trạng thái) tác động đến subject. Subject chịu trách nhiệm quản
lý danh sách các observer đã đăng ký và thông báo với chúng khi đáp ứng các sự kiện tác
động đến subject. Subject thường thông báo với observer bằng phương thức Notify (được
khai báo trong giaodiện IObserver). Subject có thể truyề
n dữ liệu cho observer trong
phương thức Notify, hoặc observer có thể cần gọi một phương thức được khai báo trong
giao diện ISubject để thu lấy thêm các chi tiết về sự kiện.
Mặc dù bạn có thể hiện thực mẫu Observer bằng C# theo cách vừa được mô tả, nhưng vì
mẫu Observer quá phổ biến trong các giải pháp phần mềm hiện đại nên C# và .NET
Framework cung cấp các kiểu sự kiện và ủ
y nhiệm để đơn giản hóa hiện thực của nó. Sử
dụng sự kiện và ủy nhiệm nghĩa là bạn không cần khai báo giaodiện IObserver và
ISubject. Ngoài ra, bạn không cần hiện thực các logic cần thiết để quản lý và thông báo
với các observer đã đăng ký—đây chính là phần dễ xảy ra lỗi nhất khi viết mã.
.NET Framework sử dụng một hiện thực cho mẫu Observer dựa-trên-sự-kiện và dựa-trên-
ủy-nhi
ệm thường xuyên đến nỗi nó được đặt một cái tên: mẫu Event. File
ObserverExample.cs chứa một hiện thực cho mẫu Event. Ví dụ này bao gồm:
10. Lớp Thermostat (subject)—theo dõi nhiệt độ hiện tạivà thông báo với observer khi
có sự thay đổi nhiệt độ.
11. Lớp TemperatureChangeEventArgs—là một hiện thực tùy biến của lớp
System.EventArgs, được sử dụng để đóng gói dữ liệu cần phân bổ cho observer.
12. Ủy nhiệm TemperatureEventHandler—định nghĩa chữ ký của phương thức mà tất
cả các observer của đối tượng Thermostat phải hiện thực, và đối tượng Thermostat
sẽ gọi phương thức này trong sự kiện thay đổi nhiệt độ.
13. Lớp TemperatureChangeObserver và TemperatureAverageObserver—là các
observer của lớp Thermostat.
Lớp TemperatureChangeEventArgs (được trình bày bên dưới) dẫn xuất từ lớp
System.EventArgs. Lớp này sẽ chứa tất cả các dữ liệu cần thiết để subject truyền cho các
observer khi nó thông báo với chúng về một sự kiện. Nếu không cần truyền dữ liệu, bạn
không phải định nghĩa một lớp đối số mới (bạn chỉ cần truyền đối số null khi dựng nên s
ự
kiện). Xem mục 16.8 để biết rõ hơn về cách hiện thực lớp đối số sự kiện tùy biến.
// Lớp đối số sự kiện chứa thông tin về sự kiện thay đổi nhiệt độ.
// Một thể hiện của lớp này sẽ được truyền cùng với mỗi sự kiện.
public class TemperatureChangeEventArgs : System.EventArgs {
// Các thành viên dữ liệu chứa nhiệt độ cũ và mới.
private readonly int oldTemperature, newTemperature;
// Phương thức khởi dựng (nhận giá trị nhiệt độ cũ và mới).
public TemperatureChangeEventArgs(int oldTemp, int newTemp) {
oldTemperature = oldTemp;
newTemperature = newTemp;
}
// Các thuộc tính dùng để truy xuất các giá trị nhiệt độ.
public int OldTemperature { get { return oldTemperature; } }
public int NewTemperature { get { return newTemperature; } }
}
Đoạn mã dưới đây trình bày cách khai báo ủy nhiệm TemperatureEventHandler. Dựa trên
khai báo này, tất cả các observer phải hiện thực một phương thức (tên không quan trọng),
trả về void và nhận hai đối số: một đối tượng Thermostat và một đối tượng
TemperatureChangeEventArgs. Trong quá trình thông báo, đối số Thermostat chỉ đến đối
tượng Thermostat đã dựng nên sự kiện, và đối số TemperatureChangeEventArgs chứa
các dữ liệu về nhiệt độ cũ và mới.
// Ủy nhiệm cho biết chữ ký mà tất cả các phương thức
// thụ lý sự kiện phải hiện thực.
public delegate void TemperatureEventHandler(Thermostat s,
TemperatureChangeEventArgs e);
Lớp Thermostat là đối tượng bị giám sát trong mẫu Observer (Event) này. Theo lý thuyết,
một thiết bị giám sát thiết lập nhiệt độ hiện tại bằng cách gọi thuộc tính Temperature trên
một đối tượng Thermostat. Khi đó, đối tượng Thermostat dựng nên sự kiện
TemperatureChange và gửi một đối tượng TemperatureChangeEventArgs đến mỗi
observer. Dưới đây là mã lệnh cho lớp Thermostat:
// Lớp mô tả một bộ ổn nhiệt, là nguồn gốc của các sự kiện thay đổi
// nhiệt độ. Trong mẫu Observer, đối tượng Thermostat là subject.
public class Thermostat {
// Trường private dùng để giữ nhiệt độ hiện tại.
private int temperature = 0;
// Sự kiện dùng để duy trì danh sách các ủy nhiệm observer và
// dựng nên sự kiện thay đổi nhiệt độ.
public event TemperatureEventHandler TemperatureChange;
// Phương thức protected dùng để dựng nên sự kiện TemperatureChange.
// Vì sự kiện chỉ có thể được phát sinh bên trong kiểu chứa nó nên
// việc sử dụng phương thức protected cho phép các lớp dẫn xuất
// có cách hành xử tùy biến và vẫn có thể dựng nên sự kiện
// của lớp cơ sở.
virtual protected void RaiseTemperatureEvent
(TemperatureChangeEventArgs e) {
if (TemperatureChange != null) {
TemperatureChange(this, e);
}
}
// Thuộc tính public dùng để lấy và thi
ết lập nhiệt độ hiện tại.
// Phía "set" chịu trách nhiệm dựng nên sự kiện thay đổi nhiệt độ
// để thông báo với tất cả các observer về thay đổi này.
public int Temperature {
get { return temperature; }
set {
// Tạo một đối tượng đối số sự kiện mới để chứa
// nhiệt độ cũ và mới.
TemperatureChangeEventArgs e =
new TemperatureChangeEventArgs(temperature, value);
// Cập nhật nhiệt độ hiện tại.
temperature = value;
// Dựng nên sự kiện thay đổi nhiệ
t độ.
RaiseTemperatureEvent(e);
}
}
}
Với mục đích minh họa mẫu Observer, ví dụ này có hai kiểu observer:
TemperatureAverageObserver (hiển thị nhiệt độ trung bình mỗi khi một sự kiện thay đổi
nhiệt độ xảy ra) và TemperatureChangeObserver (hiển thị thông tin về sự thay đổi mỗi
khi một sự kiện thay đổi nhiệt độ xảy ra). Dưới đây là mã lệnh cho hai lớp này:
// Observer hiển thị thông tin về sự thay đổi nhiệt độ
// khi có một sự kiện thay đổi nhiệt độ xảy ra.
public class TemperatureChangeObserver {
// Phương thức khởi dựng (nhận một tham chiếu đến đối tượng
// Thermostat cần được TemperatureChangeObserver giám sát).
public TemperatureChangeObserver(Thermostat t) {
// Tạo một thể hiện ủy nhiệm TemperatureEventHandler và
// đăng ký nó với Thermostat đã được chỉ định.
t.TemperatureChange +=
new TemperatureEventHandler(this.TemperatureChange);
}
// Phương thức thụ lý sự kiện thay đổi nhiệt độ.
public void TemperatureChange(Thermostat sender,
TemperatureChangeEventArgs temp) {
System.Console.WriteLine
("ChangeObserver: Old={0}, New={1}, Change={2}",
temp.OldTemperature, temp.NewTemperature,
temp.NewTemperature - temp.OldTemperature);
}
}
// Observer hiển thị thông tin về nhiệt dộ trung bình
// khi có một sự kiện thay đổi nhiệt độ xảy ra.
public class TemperatureAverageObserver {
// Sum chứa tổng các giá trị nhiệt độ.
// Count chứa số lần sự kiện thay đổi nhiệt độ xảy ra.
private int sum = 0, count = 0;
// Phương thức khởi dựng (nhận một tham chiếu đến đối tượng
// Thermostat cần được TemperatureAverageObserver giám sát).
public TemperatureAverageObserver (Thermostat t) {
// Tạo một thể hiện ủy nhiệm TemperatureEventHandler và
// đăng ký nó với Thermostat đã được chỉ định.
t.TemperatureChange +=
new TemperatureEventHandler(this.TemperatureChange);
}
// Phương thức thụ lý sự kiện thay đổi nhiệt độ.
public void TemperatureChange(Thermostat sender,
TemperatureChangeEventArgs temp) {
count++;
sum += temp.NewTemperature;
System.Console.WriteLine
("AverageObserver: Average={0:F}",
(double)sum/(double)count);
}
}
Lớp Thermostat định nghĩa một phương thức Main (được trình bày bên dưới) để chạy ví
dụ này. Sau khi tạo một đối tượng Thermostat và hai đối tượng observer, phương thức
Main sẽ nhắc bạn nhập vào một nhiệt độ. Mỗi khi bạn nhập một nhiệt độ mới, đối tượng
Thermostat sẽ báo cho observer hiển thị thông tin ra cửa sổ Console.
public static void Main() {
// Tạo một đối tượng Thermostat.
Thermostat t = new Thermostat();
// Tạo hai observer.
new TemperatureChangeObserver(t);
new TemperatureAverageObserver(t);
// Lặp để lấy nhiệt độ từ người dùng. Bất cứ giá trị
// nào không phải số nguyên sẽ khiến vòng lặp kết thúc.
do {
System.Console.Write("\n\rEnter current temperature: ");
try {
// Chuyển đầu vào của người dùng thành một số
// nguyên và sử dụng nó để thiết lập nhiệt độ
// hiện tại của bộ ổn nhiệt.
t.Temperature =
System.Int32.Parse(System.Console.ReadLine());
} catch (System.Exception) {
// Sử dụng điều kiện ngoại lệ để kết thúc vòng lặp.
System.Console.WriteLine("Terminating ObserverExample.");
return;
}
} while (true);
}
Dưới đây là kết xuất khi chạy ObserverExample.cs (các giá trị in đậm là do bạn nhập
vào):
Enter current temperature:
50
ChangeObserver: Old=0, New=50, Change=50
AverageObserver: Average=50.00
Enter current temperature:
20
ChangeObserver: Old=50, New=20, Change=-30
AverageObserver: Average=35.00
Enter current temperature:
40
ChangeObserver: Old=20, New=40, Change=20
AverageObserver: Average=36.67
. thực mẫu Observer bằng các kiểu ủy nhiệm (đóng vai trò là các con trỏ
hàm an-toàn-về-kiểu-dữ -liệu type-safe function pointer) và các kiểu sự kiện.
Cách.
thêm các thể hiện của kiểu.
Trong tất cả các mẫu được biết đến, có lẽ mẫu Singleton được biết đến nhiều nhất và
thường được sử dụng nhất. Mục đích của mẫu