641 Chương 16: Các giao diện và mẫu thông dụng • 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. • 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. • 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 giao diệ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; } } 642 Chương 16: Các giao diện và mẫu thông dụng public string Subject { get { return subject; } } } 9. 9. Hi n th c m u Singletonệ ự ẫ 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: • 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. • 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. • 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á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 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 { 643 Chương 16: Các giao diện và mẫu thông dụng 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(); 10. 10. Hi n th c m u Observerệ ự ẫ 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 giao diệ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 giao diệ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ã. 644 Chương 16: Các giao diện và mẫu thông dụng .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: • Lớp Thermostat (subject)—theo dõi nhiệt độ hiện tại và thông báo với observer khi có sự thay đổi nhiệt độ. • 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. • Ủ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 độ. • 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ề 645 Chương 16: Các giao diện và mẫu thông dụng 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 độ 646 Chương 16: Các giao diện và mẫu thông dụng // để 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); } 647 Chương 16: Các giao diện và mẫu thông dụng // 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 648 Chương 16: Các giao diện và mẫu thông dụng ("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); 649 Chương 16: Các giao diện và mẫu thông dụng } 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 650 Chương 16: Các giao diện và mẫu thông dụng . 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. 641 Chương 16: Các giao diện và mẫu thông dụng • 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ự. đâ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ề 645 Chương 16: Các giao