Việc lựa chọn định dạng mẫu thiết kế

Một phần của tài liệu Phát triển mẫu thiết kế phần mềm và ứng dụng (Trang 74)

Nhƣ trên đã trình bày, có nhiều định dạng khác nhau để mô tả một mẫu thiết kế và trên thực tế có thể còn có nhiều hơn thế nữa. Mỗi tài liệu đã đƣợc biết về mẫu thiết kế thƣờng sử dụng các định dạng riêng khác nhau và đôi khi có những biến đổi từ những định dạng cơ bản. Và có thể nói rằng, việc lựa chọn định dạng mẫu thiết kế nào là phụ thuộc vào sự lựa chọn cá nhân. Các định dạng khác nhau đƣợc sử dụng bởi các tác giả khác nhau bởi các kiểu viết khác nhau dựa theo sự riêng biệt cá nhân khác nhau. Điều quan trọng nhất là tìm một định dạng phù hợp với kiểu viết, với những ý tƣởng đang cần đƣợc bao quát.

Để có thể tìm đƣợc định dạng mẫu phù hợp, bƣớc đầu tiên là đọc nhiều sách mẫu thiết kế khác nhau và tập trung vào nội dung, nhƣng tự hỏi định dạng nào có vẻ tiện lợi nhất. Để thực sự phù hợp, cần đọc lƣớt qua từ đầu đến kết thúc, và đôi khi có thể đạt đƣợc điều này trong công việc.

Khi đã có ý tƣởng định dạng mong muốn, hãy bắt đầu viết. Cố gắng thử với vài định dạng khác nhau. Một cách hữu ích là thử viết một mẫu theo nhiều định dạng khác nhau để xem định dạng nào có vẻ phù hợp nhất. Nhờ ai đó xem xét và đánh giá xem định dạng nào tốt hơn.

Các mẫu biến đổi đáng kể về kích cỡ của chúng theo các định dạng khác nhau. Định dạng Portland thƣờng nhận một mẫu đƣợc thực hiện trong nhiều đoạn, POSA có thể tiếp tục cho hàng chục trang. Lựa chọn của bạn ở đây phụ thuộc vào mức độ chi tiết mong muốn. Nếu muốn thăm dò vấn đề thực thi và cung cấp mã ví dụ, chắc chắn có các mẫu dài hơn. Trong trƣờng hợp này định dạng có cấu trúc thƣờng phù hợp hơn.

3.5. Tổng kết chƣơng

Khi gặp các tình huống phát sinh, đồng thời dựa theo các nguyên lý thiết kế đã đƣợc trình bày trong chƣơng 2, ta có thể hình dung đƣợc một số vấn đề cho mẫu thiết kế dự định đƣa ra. Tuy nhiên, đó vẫn còn là những ý tƣởng. Để viết đƣợc ra những ý tƣởng đó, cần có một định hƣớng hay một cấu trúc nào đó. Trong tình huống đó, ta cũng có thể viết theo một cấu trúc riêng theo ý muốn. Tuy nhiên cần có sự cân nhắc về thời gian, sự tối ƣu cũng nhƣ việc chấp nhận của đọc giả. Trên cơ sở đó, tác giả luận văn đã đƣa ra một số cấu trúc hiện đang đƣợc sử dụng rộng rãi. Trƣớc hết đó là các thành phần cơ bản hình thành của một mẫu thiết kế. Sau đó là 6 định dạng mẫu đã

68

đƣợc đƣa ra và đƣợc chấp nhận sử dụng rộng rãi. Ở đây luận văn tập trung mô tả và đề xuất sử dụng một định dạng mẫu rộng rãi nhất, đó là định dạng GOF, đã đƣợc Erich Gamma cùng các cộng sự sử dụng để mô tả 23 mẫu thiết kế nổi tiếng của mình.

69

CHƢƠNG 4. PHÁT TRIỂN MẪU THIẾT KẾ VÀ ỨNG DỤNG 4.1. Các mẫu thiết kế đối tƣợng

4.1.1. Mẫu đối tượng trống

Mẫu đối tƣợng trống (Null Object pattern) cung cấp một đối tƣợng nhƣ là sự thay thế cho việc thiếu một đối tƣợng của một kiểu đƣợc đƣa ra. Null object cung cấp cách cƣ xử không làm gì cả một cách thông minh, ẩn sau chi tiết của các cộng tác viên của nó.

Tên khác: Stub, Active Nothing

Khi một Client gọi một phƣơng thức hay một biến mà có thể là null có thể phát sinh các ngoại lệ. Để bảo vệ hệ thống khỏi các vấn đề không mong muốn, chúng ta phải viết mã để ngăn cản việc gọi phƣơng thức hoặc giá trị null:

if (someObject != null)

someObject.doSomething(); else

performAlternativeBehavior();

Việc lặp lại các mã kiểm tra nhƣ vậy có thể đƣợc thực hiện. Tuy nhiên việc lặp lại nhiều nơi có thể làm phồng lên hệ thống với các đoạn mã không cần thiết. So sánh với đoạn mã mà không ràng buộc bởi logic null, đoạn mã bị ràng buộc bởi nó thƣờng yêu cầu nhiều về việc lĩnh hội và suy nghĩ làm thế nào để mở rộng nó. Logic null cũng không đáp ứng đƣợc yêu cầu cung cấp sự bảo vệ null cho mã mới. Nếu một đoạn mã mới đƣợc thêm vào và lập trình viên quên không tính đến logic null, các lỗi có thể xảy ra. Mẫu Null Object cung cấp giải pháp trong trƣờng hợp này. Nó loại bỏ việc kiểm tra xem một trƣờng hay biến có giá trị null hay không bởi việc tạo khả năng luôn luôn gọi đến trƣờng hay biến một cách an toàn. Cách thức để thực hiện điều này là gán cho trƣờng hoặc biến một đối tƣợng phù hợp vào thời điểm phù hợp. Khi một trƣờng hoặc biến là giá trị null, ta cho nó tham chiếu tới một thực thể Null Object mà cung cấp cách ứng xử mặc định là không làm gì cả hoặc không gây lỗi.

Khóa của Null Object pattern là một lớp trừu tƣợng xác định giao diện cho tất cả các đối tƣợng của kiểu này. Null Object đƣợc thực thi nhƣ là lớp con của lớp trừu tƣợng này. Bởi vì nó tuân theo giao diện của lớp trừu tƣợng, nó có thể đƣợc sử dụng ở bất kỳ nơi nào mà kiểu đối tƣợng này đƣợc cần đến.

Đôi khi nghĩ rằng các Null Object là đơn giản và “ngu ngốc” nhƣng thực sự một null object luôn luôn biết chính xác cái gì cần đƣợc thực hiện mà không tƣơng tác với bất kỳ đối tƣợng khác. Vì vậy thực sự chúng rất thông minh.

Sử dụng mẫu Null Object khi:

 Một đối tƣợng cần đến một cộng tác viên. Mẫu Null Object không đƣa ra cộng tác viên này, nó sử dụng cộng tác viên mà đã tồn tại.

70

 Một số bản thể cộng tác viên sẽ không làm gì cả.

 Bạn muốn trừu tƣợng việc xử lý của null từ client. Cấu trúc:

Hình 4.1. Sơ đồ lớp mẫu Null Object

Thành phần:

 Client: cần đến một cộng tác viên

 AbstractObject: khai báo giao diện cho cộng tác viên của client. Thực thi các cƣ xử mặc định cho giao diện chung của tất cả các lớp cho phù hợp.

 RealObject: định nghĩa một lớp con cụ thể của AbstractObject mà các bản thể của chúng cung cấp cách cƣ xử có ích mà client mong đợi.

 NullObject:

 Cung cấp một giao diện giống với giao diện của AbstractObject để một null object có thể đƣợc thay thế cho một đối tƣợng thực.

 Thực thi giao diện của nó để không làm gì cả. Điều đó chính xác nghĩa là không làm gì phụ thuộc vào cách cƣ xử mà client đang mong đợi.

 Khi có nhiều hơn một cách để không làm gì, nhiều hơn một lớp NullObject có thể đƣợc yêu cầu

Các client sử dụng giao diện lớp AbstractObject để giao tiếp với các cộng tác viên của nó. Nếu bên nhận là một RealObject, yêu cầu đƣợc xử lý để cung cấp cƣ xử thực. Nếu bên nhận là một NullObject, yêu cầu đƣợc xử lý bởi không làm gì cả hay ít nhất cung cấp một kết quả null.

Mẫu Null Object xác định cấp bậc lớp bao gồm các đối tƣợng thực và các đối tƣợng null. Các đối tƣợng null có thể đƣợc sử dụng ở nơi của các đối tƣợng thực khi đối tƣợng đƣợc mong đợi không làm gì cả. Mỗi khi mã client mong đợi một đối tƣợng thực, nó cũng có thể lấy một null object.

71

Tạo đơn giản mã client. Các client đối xử với các cộng tác viên thực và các cộng tác viên null cùng dạng. Các client thông thƣờng không biết (và sẽ không quan tâm) xem là chúng đang đƣợc đối xử với một cộng tác viên null hay thực. Điều này làm đơn giản hóa mã client bởi vì nó tránh phải viết mã kiểm thử xử lý cộng tác viên null đặc biệt.

Có vài vấn đề cần xem xét khi thực thi mẫu null object:

 Lớp null object thƣờng đƣợc thực thi nhƣ là một singleton. Bởi vì một null object thƣờng không có trạng thái nào, trạng thái của nó không thể thay đổi, vì vậy nhiều bản thể là giống nhau. Thay vì sử dụng đa bản thể giống nhau, hệ thống có thể chỉ sử dụng một bản thể đơn một cách lặp lại.

 Nếu một vài client mong đợi null object không làm gì một cách và một số cách ứng xử khác, các lớp NullObject sẽ đƣợc yêu cầu.

 Đối tƣợng null nhƣ là Strategy đặc biệt. Một đối tƣợng null có thể là một trƣờng hợp của mẫu Strategy. Strategy xác định vài lớp ConcreteStratey nhƣ là cách tiếp cận khác nhau cho việc hoàn thành một việc. Nếu một trong số các cách tiếp cận đó là không làm gì, ConcreteStrategy đó là một NullObject.

 Đối tƣợng null nhƣ là một State đặc biệt. Một đối tƣợng null có thể là một trƣờng hợp đặc biệt của mẫu State. Thông thƣờng, mỗi ConcreteState có vài phƣơng thức không làm gì bởi vì chúng không phù hợp cho state đó. Trên thực tế, một phƣơng thức đƣợc đƣa ra thƣờng đƣợc thực thi để làm một số thứ có ích trong hầu hết các trạng thái nhƣng để không làm gì trong ít nhất một trạng thái. Nếu một ConcreteState đặc biệt thực thi hầu hết các phƣơng thức của nó để không làm gì hay ít nhất đƣa ra các kết quả null, nó trở thành một state không làm gì và hiểu theo nghĩa thông thƣờng là một state null.

Mã ví dụ:

public abstract class NullObjectList{ public abstract int size();

public abstract NullObjectList remove(int rem);

public abstract NullObjectList append(int toAppend); public abstract void printList();

}

class Node: NullObjectList{ int data;

NullObjectList next;

public Node(int data, NullObjectList next){ this.data = data;

this.next = next; }

72 public override int size(){

return 1 + next.size(); }

public override NullObjectList remove(int rem){ if (this.data == rem)

return this.next.remove(rem); else

return new Node(this.data, this.next.remove(rem)); }

public override NullObjectList append(int toAppend){

return new Node(this.data,

this.next.append(toAppend)); }

public override void printList(){ Console.WriteLine(this.data); this.next.printList();

} }

class Empty : NullObjectList{ public Empty() { }

public override int size(){ return 0;

}

public override NullObjectList remove(int rem){ return new Empty();

}

public override NullObjectList append(int toAppend){ return new Node(toAppend, new Empty());

}

public override void printList(){ return;

} }

class TestClass{

public static void Main(){ string input;

int numInput;

NullObjectList myList = new Empty(); bool isInput = true;

while (isInput){

Console.WriteLine("Select An Option: "); Console.WriteLine("1. Append a Number"); Console.WriteLine("2. Remove a Number"); Console.WriteLine("3. Output List Size"); Console.WriteLine("4. Print List");

73

Console.WriteLine("Press Any Other Number to Exit Program"); Console.WriteLine(""); Console.Write("Selection: "); input = Console.ReadLine(); numInput = Convert.ToInt32(input); Console.WriteLine(""); switch (numInput){ case 1: Console.Write("Input a Number: ") input = Console.ReadLine(); numInput = Convert.ToInt32(input); myList = myList.append(numInput); Console.WriteLine(""); break; case 2: Console.WriteLine("Input a Number:"); input = Console.ReadLine(); numInput = Convert.ToInt32(input); myList = myList.remove(numInput); Console.WriteLine(""); break; case 3: Console.WriteLine("List Size is " + myList.size() + " elements"); Console.WriteLine(""); break; case 4: Console.WriteLine(""); myList.printList(); Console.WriteLine(""); break; default: isInput = false; break } } } }

Singleton thƣờng đƣợc sử dụng để thực thi một đối tƣợng null bởi vì đa bản thể sẽ tác động chính xác cùng cách và không có trạng thái trong mà có thể thay đổi.

Flyweight có thể đƣợc sử dụng khi đa đối tƣợng null đƣợc thực thi nhƣ là bản thể của lớp NullObject đơn.

74

State thƣờng sử dụng đối tƣợng null để biểu diễn trạng thái trong đó client sẽ không làm gì.

Iterator có thể có đối tƣợng null nhƣ là một trƣờng hợp đặc biệt mà không lặp lại trên bất cứ cái gì.

Adapters có thể có đối tƣợng null nhƣ là một trƣờng hợp đặc biệt mà giả vờ làm thích nghi.

4.1.2. Mẫu đối tượng vai trò

Mẫu đối tƣợng vai trò (Role Object Pattern) làm thích ứng một đối tƣợng với các nhu cầu của client khác nhau thông qua các đối tƣợng vai trò đƣợc gắn kết một cách trong suốt, mỗi đối tƣợng biểu diễn một vai trò trong ngữ cảnh của client. Đối tƣợng quản lý tập các vai trò của nó một cách động. Bởi việc biểu diễn vai trò nhƣ là các đối tƣợng riêng, các ngữ cảnh khác nhau đƣợc giữ riêng rẽ và cấu hình hệ thống đƣợc đơn giản hơn.

Một hệ thống hƣớng đối tƣợng điển hình dựa trên một tập các sự trừu tƣợng chính, mà đƣợc mô hình hóa bởi một lớp bao gồm các trạng thái và cách ứng xử. Điều này thƣờng làm việc tốt cho việc thiết kế các ứng dụng nhỏ. Khi muốn mở rộng hệ thống thành một bộ các ứng dụng tích hợp, chúng ta phải xử lý với các client khác nhau mà cần các tầm nhìn xác định ngữ cảnh trong các sự trừu tƣợng chính của chúng ta.

Giả sử rằng chúng ta đang phát triển phần mềm hỗ trợ cho phòng Đầu tƣ của ngân hàng. Một trong số các sự trừu tƣợng chính đƣợc biểu diễn là khái niệm khách hàng. Do đó, mô hình thiết kế của chúng ta sẽ có một lớp Customer. Giao diện lớp cung cấp các phép toán để quản lý các thuộc tính nhƣ là tên, địa chỉ, tiền tiết kiệm và tài khoản.

Giả sử rằng phòng Tín dụng cũng cần phần mềm hỗ trợ nghiệp vụ. Có vẻ nhƣ là thiết kế lớp của chúng ta không thỏa đáng khi xử lý một khách hàng đóng vai trò ngƣời đi vay. Hiển nhiên, chúng ta phải cung cấp các phép toán và thuộc tính để quản lý các tài khoản, số tiền...

Việc tích hợp nhiều tầm nhìn xác định ngữ cảnh vào cùng một lớp sẽ có khả năng dẫn đến các sự trừu tƣợng chính với các giao diện bị phồng lên. Các giao diện nhƣ vậy sẽ khó hiểu và khó bảo trì. Những thay đổi không đoán trƣớc khó đƣợc xử lý một cách trôi chảy và sẽ gây ra nhiều sự biên dịch lại. Các thay đổi đối với một phần của giao diện lớp xác định client có thể ảnh hƣởng tới các client trong các hệ thống con khác cũng nhƣ là các ứng dụng khác.

Một giải pháp đơn giản là mở rộng lớp Customer bởi thêm vào các lớp con Borrower và Investor tƣơng ứng với ngữ cảnh xác định ngƣời đi vay và ngƣời đầu tƣ.

75

Từ một điểm nhận dạng đối tƣợng của tầm nhìn, việc phân chia lớp ngụ ý rằng hai đối tƣợng của các lớp con khác nhau là không đồng nhất. Do đó một khách hàng đóng vai trò cả ngƣời đầu tƣ và ngƣời đi vay đƣợc biểu diễn bởi hai đối tƣợng khác nhau với các định danh phân biệt. Nếu hai đối tƣợng là đồng nhất, các thuộc tính đƣợc thừa kế của chúng phải đƣợc kiểm tra thƣờng xuyên về tính nhất quán. Tuy nhiên, chúng ta sẽ gặp phải một số vấn đề trong trƣờng hợp tìm kiếm đa hình, chẳng hạn khi muốn hình thành một danh sách tất cả các khách hàng trong hệ thống, cùng một đối tƣợng khách hàng sẽ xuất hiện lặp lại trừ khi chúng ta loại bỏ đối tƣợng lặp.

Mẫu Role Object gợi ý việc mô hình các tầm nhìn xác định ngữ cảnh của một đối tƣợng nhƣ là các đối tƣợng vai trò riêng rẽ, đƣợc gắn kết và loại bỏ một cách động từ đối tƣợng lõi. Chúng ta gọi kết quả cấu trúc đối tƣợng phức hợp bao gồm đối tƣợng lõi và các đối tƣợng vai trò của nó là một chủ thể. Một chủ thể thƣờng đóng nhiều vai trò và cùng một vai trò có thể do nhiều chủ thể đóng vai. Chẳng hạn hai khách hàng khác nhau đóng vai trò của ngƣời đi vay và ngƣời đầu tƣ tƣơng ứng. Cả hai vai trò cũng có thể đƣợc đóng vai bởi một đối tƣợng khách hàng.

Hình 4.2. Sơ đồ lớp nghiệp vụ ngân hàng

Một sự trừu tƣợng chính nhƣ là Customer đƣợc định nghĩa nhƣ là lớp cha trừu tƣợng. Nó phục vụ nhƣ là một giao diện đơn thuần không xác định các trạng thái thực thi. Lớp Customer xác định các thao tác để xử lý địa chỉ, tài khoản khách hàng, và định nghĩa các giao thức tối thiểu cho việc quản lý các vai trò. Lớp con CustomerCore thực thi giao diện Customer.

Lớp cha thông dụng cho các vai trò xác định khách hàng đƣợc cung cấp bởi lớp trừu tƣợng CustomerRole, hỗ trợ giao diện Customer. Các lớp con cụ thể của lớp

76

CustomerRole, chẳng hạn nhƣ Borrower và Investor định nghĩa và thực thi giao diện cho các vai trò xác định. Đó là các lớp con đƣợc khởi tạo lúc thực thi. Lớp Borrower

Một phần của tài liệu Phát triển mẫu thiết kế phần mềm và ứng dụng (Trang 74)

Tải bản đầy đủ (PDF)

(113 trang)