Thursday, 12 April 2012

CHƯƠNG VIII: XỬ LÝ TẬP HỢP VỚI MẪU ITERATOR VÀ MẪU COMPOSITE

copyright from: congdongcviet.com

Trích dẫn:
Trong chương này:
- Sử dụng mẫu Iterator (đối tượng lặp lại)
- Tạo một đối tượng Iterator
- Duyệt qua danh sách các phó giám đốc bằng một Iterator nội tại
- Hiểu được mẫu Composite (tập hợp)
- Sử dụng một Iterator bên trong một Composite
Giám đốc điều hành của công ty GianDataPool, một công ty mà bạn mới chuyển đến với vị trí tư vấn, vừa đi khẽ vào phòng làm việc của bạn và nói lầm bầm gì đó.

“Gì vậy?” bạn hỏi.

Vị giám đốc nhìn quanh với vẻ mặt bí mật, và nói “Tôi có một dự án tuyệt mật dành cho bạn”

“Tuyệt mật?” bạn nói “Nó nói về cái gì?”

“Đừng to tiếng!” vị giám đốc nói khẽ. “Chúng ta cần một người khách quan cho chuyện này, Vì vậy tôi mới gặp anh. Chúng ta dường như đang gặp phải một số vấn đề với việc quản trị và chúng ta cần phải theo dõi các phó giám đốc – Không ai được biết việc này. Bây giờ, có thể có hai hay vài vị phó giám đốc làm việc như một lập trình viên vậy”

“Thừa thầy thiếu thợ”, bạn thở dài “Chuyện dài tập của các công ty”

“Chúng ta bắt đầu với khu vực bán hàng,” vị giám đốc nói khẽ “Anh có thể viết một chương trình duyệt qua hết hồ sơ và in chúng ra chứ?”

“Còn hơn thế nữa”, bạn nói. ”Tôi sẽ sử dụng mẫu Iterator”
Chương này nói về hai mẫu có quan hệ mật thiết với nhau: mẫu Iterator và mẫu Composite. Mẫu Iterator cung cấp cho bạn cách thức truy cập một bộ phận bên trong một đối tượng mà không cần phải hiểu rõ cấu trúc nội tại của đối tượng đó. Ví dụ, hãng Sun đã giới thiệu một kiểu tập hợp trong việc biểu diễn các mối quan hệ trong ngôn ngữ Java, những tập hợp này cho phép bạn tạo iterator – một đối tượng đặc biệt được thiết kế cho phép bạn truy cập một phần tử của tập hợp – để cung cấp một cách thức truy cập dễ dàng.
Mẫu Composite cũng nói về tập hợp. Với mẫu Composite, ý tưởng là bạn có thể một cấu trúc hình cây nơi mà từng đối tượng sẽ thuộc về một cái cây -là một nút lá không có nút con, hoặc là một nhánh cây với nhiều nút lá con – để có thể xử lý trong cùng một cách. Mẫu Composite được thiết kế cho phép bạn xử lý nhiều đối tượng khác chủng loại trong cùng một tập hợp theo cùng một cách, và một đối tượng lặp iterator lại vô tình phù hợp tại đây – dùng để xử lý từng phần tử của một nhánh cây – ví dụ, bạn có thể duyệt qua hết cây. Chúng ta sẽ thảo luận về hai mẫu trong chương này.
Truy cập đối tượng với mẫu Iterator

Khi bạn làm việc với một tập hợp nhiều đối tượng, mẫu Iterator là một giải pháp tốt. Hàng ngày, bạn phải làm việc với nhiều loại tập hợp như cấu trúc cây, cây nhị phân, mảng, vòng đệm, bảng băm, danh sách mảng và vân vân… Cách thức mà tập hợp này lưu trữ đối tượng của nó rất khác nhau, và nếu bạn muốn truy cập dữ liệu của những đối tượng này, bạn phải học những kỹ thuật khác nhau cho từng loại tập hợp.
Và đó là nơi mẫu Iterator xuất hiện. Bạn có thể sử dụng một giao diện interface được xác định rõ ràng để truy cập tới từng phần tử của tập hợp. Trong những năm qua, các phương pháp cơ bản đã dần trở nên thích hợp hơn, và chúng cũng xuất hiện xuyên suốt chương này. Sử dụng những phương pháp này, bạn có thể truy xuất tới các phần tử trong tập hợp theo cách cơ bản nhất.
Ghi nhớTheo sách của Gang of Four (Gof), bạn có thể sử dụng mẫu thiết kế Iterator để “Cung cấp một cách thức truy cập tuần tự tới các phần tử của một đối tượng tổng hợp, mà không cần phải tạo dựng riêng các phương pháp truy cập cho đối tượng tổng hợp này”

Nói cách khác, một Iterator được thiết kế cho phép bạn xử lý nhiều loại tập hợp khác nhau bằng cách truy cập những phần tử của tập hợp với cùng một phương pháp, cùng một cách thức định sẵn, mà không cần phải hiểu rõ về những chi tiết bên trong của những tập hợp này.

Gợi ý: Mẫu thiết kế Iterator đặc biệt quan trọng khi tập hợp bạn đang xây dựng được tạo thành từ những tập hợp con riêng rẽ, ví dụ khi bạn chỉnh sửa bảng băm với danh sách mảng, chẳng hạn.

Thông tin: Iterator thường được viết trong Java như là những lớp độc lập. Tại sao những Iterator có thể làm việc được trong các tập hợp khác nhau? Chúng có thể, nhưng trong Java, còn ngôn ngữ khác, chúng không thể. Ý tưởng thiết kế này là một trong những kỹ thuật được gọi là “đơn trách nhiệm” – một lớp chỉ có duy nhất một công việc để làm. Hãy suy nghĩ rằng tập hợp duy trì các phần tử, một iterator cung cấp cách thức làm việc với các phần tử đó. Tách biệt trách nhiệm giữa các lớp rất hữu dụng khi một lớp bị thay đổi – Nếu có quá nhiều thứ bên trong một lớp đơn lẻ, sẽ rất khó khăn để viết lại mã nguồn. Khi diễn ra sự thay đổi, một lớp “đơn trách nhiệm” sẽ chỉ có một lý do duy nhất để thay đổi.

Truy cập đối tượng của bạn với một Iterator

Bạn bắt đầu làm việc với rắc rối giám đốc, đó là phải theo dõi các phó giám đốc. Trong trường hợp này, bạn quyết định lưu các phó giám đốc vào trong một tập hợp, với một tập hợp các chức năng cho phép truy xuất các vị này. Trong phiên bản đầu tiên này, các chức năng cơ bản mà một Iterator phải có như sau:



Ngày này Java đã hỗ trợ một giao diện iterator trong java.util.Iterator, được định nghĩa với ba phương pháp sau:



Hàm next trả về phần tử kế tiếp trong tập hợp, hàm hasNext trả về giá trị True nếu vẫn còn phần tử trong tập hợp và trả về false trong trường hợp ngược lại, hàm remove cho phép bạn gỡ bỏ một phần tử trong tập hợp.
Đó là cách Iterator làm việc – Nó cung cấp một giao diện đơn giản, nhất quán để làm việc với các tập hợp khác nhau. Giả sử rằng khách hàng phải làm việc với một tập hợp phức tạp và rắc rối ( như hình sau) và không biết cách thức làm việc với nó như thế nào.


Khách hàng có thể sử dụng iterator để làm cầu nối với tập hợp, và khách hàng có thể sử dụng các phương thức cơ bản của Iterator để giao tiếp với tập hợp. Như hình sau:



Công việc đầu tiên khi lưu trữ dữ liệu các phó giám đốc cũng giống cách trên. Bạn quyết định, đầu tiên là tạo một lớp lưu trữ thông tin cho từng phó giám đốc, với tên lớp VP ( Vice President – phó giám đốc, phó chủ tịch.. )

Bạn phải tạo bốn thành phần quan trọng trong lớp này, bao gồm:
  • Một hàm khởi dựng cho phép truyền giá trị tên của vị phó này
  • Tên khu vực làm việc của vị phó
  • Hàm getName trả về tên của người này
  • Ham print cho phép in ra thông tin của vị phó này, bao gồm tên và khu vực làm việc



Lớp này đã đóng gói thông tin một phó giám đốc. Bây giờ ta phải lưu trữ tất cả phó giám đốc trong một lớp.

Thu thập các phó giám đốc vào một tập hợp:
Trong ví dụ này, bạn tạo tập hợp các phó giám đốc dựa trên mảng căn bản của Java. List do dùng kiểu căn bản này, thay vì dùng các chức năng có sẵn trong Java như vector, danh sách mảng, bản đồ băm … với phần tử Iterator có sẵn, đó là việc tạo Iterator từ đầu để làm việc với tập hợp thì hơi ngớ ngẩn, nhưng rất tốt để hiểu về mẫu này.
Bạn quyết định lưu thông tin các phó giám đốc trong từng khu vực, ví dụ khu vực bán hàng Sales, trong lớp tên là Division

Trích dẫn:
public class Division
{
...
...
...
}
Hàm khởi dựng của lớp Division sẽ lưu trữ tên của khu vực này, ví dụ Sales, và hàm getNames sẽ trả về tên đó



Các phó giám đốc sẽ được lưu trong một mảng, tên là vPs, và bạn có thể thêm một phó giám đốc bằng hàm add như sau:



Nói cách khác, đối tượng Division là một tập hợp, và đối tượng phó giám đốc VP là một phần tử của tập hợp này. Để thêm một iterator, tập hợp cần phải có một hàm – tên bạn có thể đặt tùy ý – ví dụ như iterator chẳng hạn (có thể tên bao gồm việc tạo createIterator và việc nhận getIterator). Hàm này sẽ chuyển mảng các phó giám đốc vào hàm khởi dựng của lớp iterator, ta gọi tên lớp này là lớp DivisionIterator. Mã như sau:



Bước tiếp theo là tạo iterator, lớp DivisionIterator, cho phép bạn lặp xuyên qua tập hợp các phó giám đốc trong tập hợp.

Tạo lớp Iterator

Lớp iterator, DivisionIterator, hiện thực ba hàm trong giao diện java.util.Iterator : hàm nex, hàm hasNext, và hàm remove. Mã như sau:



Hàm khởi dựng chấp nhận một mảng các phần tử VP và lưu trữ lại như sau:



Bây giờ bạn phải hiện thực các giao diện của Iterator. Hàm next trả về phần tử kế tiếp trong mảng. Mã như sau:



Hàm hasNext trả về true nếu có phần tử kế tiếp trong tập hợp, ngược lại trả về false. Trong trường hợp này, bạn phải kiểm trả đã ở cuối của dãy chưa? Bởi vì bạn đang làm việc với một mảng cố định, bạn cũng phải kiểm tra nếu phần tử kế tiếp là phần tử trống (null) – và bạn cũng phải kiểm tra xem mảng có phải là rỗng hay không. Hàm hasNext như sau:



Hiện tại bạn muốn mảng phó giám đốc này chỉ đọc, bạn tiếp tục hiện thực hàm remove với nội dung rỗng như sau:



Tuyệt vời. Bạn đã có đối tượng phó giám đốc, một khu vực thể hiện như một tập hợp các phó giám đốc, và một đối tượng lặp Iterator. Việc cuối cùng là đưa tất cả chúng vào một chương trình và bắt đầu lặp qua các phó giám dốc

Lặp qua các phó giám đốc

Xem mã sau:



Mã nguồn bắt đầu từ việc tạo khu vực bán hàng Sales và thêm vào một vài vị phó giám đốc:



Sau đó ta tạo một iterator bằng cách gọi hàm iterator và sử dụng các hàm hasNext, next để duyệt qua từng phó giám đốc trong tập hợp và hiển thị thông tin từng người một.



Kết quả là, chương trình in ra toàn bộ thông tin các phó giám đốc:



Đặt mọi thứ vào trong tập hợp composites

Giám đốc của GianDataPool Inc, chạy ào vào văn phòng bạn với vẻ đắc thắng và nói lớn: “Tôi muốn sa thải một vài phó giám đốc!” 

“Tốt,” bạn nói.

“Tôi muốn làm thêm nữa. Bây giờ tôi cần in ra tất cả thông tin phó giám đốc của toàn bộ công ty – không chỉ khu vực bán hàng, mà là toàn bộ các khu vực.”

“Tất cả các khu vực?” bạn hỏi.

“Vâng. Và cả các phó giám đốc hoạt động độc lập, không trực thuộc vào một khu vực nào ”.

“Hmm”, bạn nói ,”Đã đến lúc sử dụng một mẫu thiết kế mới”.

“Đợi đã”, giám đốc nói “Nhớ kỹ rằng đây là một vụ cắt giảm chi phí đó”

“Tôi sẽ sử dụng mẫu tổng hợp composites”, bạn nói.

“Có tốn nhiều chi phí không?”

“Không” bạn nói. “nhưng tôi phải làm nhiều thôi”

Bạn đã hiểu rõ rắc rối. bây giờ bạn phải xử lý toàn bộ công ty, không chỉ là một phân khu. Toàn bộ công ty có nhiều khu vực với các phó giám đốc, và khu vực này có thể bao gồm cả khu vực khác – và bao gồm cả các phó giám đốc tự do nữa. Hình sau chỉ ra mô hình công ty:



Vì vậy, giờ đây bạn đang làm việc với một tổ chức phức tạp, không chỉ là một khu vực bán hàng Sales nữa. Và giám đốc điều hành muốn bạn in ra toàn bộ công ty, vì vậy bạn không chỉ cần hàm print của đối tượng VP, mà từng khu vực phải có một hàm print riêng. OK, đã đến lúc sử dụng mẫu tổng hợp Composite.

Bạn muốn có một hàm print, mà khi được gọi, nó sẽ in ra thông tin của một phó giám đốc, một phòng ban, hoặc cả tổ chức. Mẫu Composites là mẫu nói về việc tạo ra một cấu trúc hình cây nơi mà từng lá trong cây, có thể được sử dụng trong cùng một cách với nhánh của nó ( nhánh là cấu trúc chứa nhiều lá, và giống các nhánh khác ). Ý tưởng chính ở đây là, để làm mọi chuyện dễ dàng, bạn có thể xử lý các nút lá và tập hợp các nút lá trong một cái cây theo cùng một cách.

Ghi nhớSách của GoF nói rằng, bạn sử dụng mẫu Composites để “Tạo ra các đối tượng trong một cấu trúc hình cây để biểu diễn cho một cấu trúc phân cấp. Mẫu Composites cho phép khách hàng xử lý một đối tượng riêng hoặc toàn bộ đối tượng theo cùng một cách”

Đó là những gì bạn cần – một mẫu thiết kế cho phép bạn xử lý các nút lá hoặc các nhánh của cấu trúc cây theo cách giống nhau bởi vì bạn muốn có thể in ra thông tất cả các phó giám đốc riêng lẻ, trong một khu vực, hoặc cả công ty, chỉ bằng cách gọi hàm print.
Mẫu thiết kế Composites rất phù hợp với mẫu Iterator bởi vì khi bạn gọi từng khu vực để in chính nó, nó có thể dễ dàng duyệt qua từng phó giám đốc một. Đó là đặc điểm điển hình của mẫu Composite – khi bạn yêu cầu một nhánh thực hiện một hành động gì đó , nó sẽ lặp qua tất cả các lá con và nhánh con của nó.
Ý tưởng đằng sau của mẫu Composite là việc xử lý các nút lá và nhánh trong một cấu trúc hình cây sẽ giống nhau. Điều này giúp cho việc xử lý các cấu trúc phức tạp theo dạng hình cây sẽ dễ dàng hơn bởi vì bạn không cần phải thiết lập các hàm khác nhau cho từng phần của cấu trúc.
Để thực hiện mẫu Composite, sách của GoF khuyên rằng bạn nên sử một lớp trừu tượng như là một lớp cơ sở cho cả nút lá và các nhánh trong cấu trúc cây. Việc làm này giúp cho các nút lá và các nhánh sẽ có chung một tập hợp các hàm, đó là tất cả những gì mẫu Composite muốn nói tới. Sách của GoF đề nghị bạn sử dụng một lớp trừu tượng, tuy nhiên bạn cũng có thể sử dụng một giao diện interface để làm việc này trong Java.
Tất cả bắt đầu với một lớp trừu tượng

Tôi sẽ theo chỉ dẫn của sách GoF và tạo một lớp trừu tượng cho cả phó giám đốc cũng như khu vực, lớp này tên Corporate. Bên dưới là mã nguồn của lớp này. Chú ý nó cũng có hàm add, và hàm iterator để trả về một iterator, và một hàm print:



Đây là lớp dùng để kế thừa cho cả các nút lá phó giám đốc và các nhánh cây khu vực.

Tạo nút lá phó giám đốc

Lớp VP bạn tạo trước đây phải chỉnh sửa một chút, để bạn có thể thống nhất cách làm việc với cả phó giám đốc và khu vực trong cùng một cây tổ chức, theo cách mẫu Composite đã nói. Đặc biệt, bạn phải kế thừa lớp VP từ lớp trừu tượng Corporate mà bạn đã tạo trong phần trên



Lớp VP trước đây chỉ chứa tên và khu vực làm việc của phó giám đốc và hàm print để in ra thông tin này. Nhưng để khách hàng có thể xử lý lớp VP cùng cách với các khu vực division, bạn cần thêm một hàm tạo iterator cho nó. Bởi vì một phó giám đốc không chứa bất cứ phó giám đốc nào, nên iterator được tạo ra chỉ tạo trả về một đối tượng phó giám đốc duy nhất khi bạn gọi hàm next và hàm hasNext luôn trả về giá trị sai false. Mã như sau:



Lớp VPIterator sẽ như thế nào? Rất dễ dàng, bạn chỉ cần hiện thực giao diện Iterator của Java, đưa vào đối tượng VP thông qua hàm khởi dựng, tạo hàm next trả về đối tượng đó và hàm hasNext trả về giá trị false, như mã sau:



Bây giờ khách hàng có thể xử lý nút lá phó giám đốc giống như một nhánh cây khu vực. Thực tế là iterator nút lá phó giám đốc chỉ trả về duy nhất một phó giám đốc, nhưng bây giờ bạn đã có một iterator cho từng nút lá, bạn không phải chỉnh sửa mã nguồn để có thể vừa làm việc với nút lá vừa làm việc với các phân khu.

Tạo một nhánh cây các khu vực

Từng nhánh cây trong cấu trúc cây công ty là một khu vực trong công ty, mà có thể bao gồm nhiều phó giám đốc hoặc khu vực con. Để xử lý khu vực, bạn quyết định chỉnh sửa lớp Division ( đã tạo trước đây ) theo cách mở rộng từ lớp Corporate, như cách đã làm với lớp VP, mã nguồn như sau:



Phần còn lại của lớp Division sẽ giống như trước, ngoài trừ bạn phải chuyển đổi một chút để phù với đối tượng Corporate. Trước đây, lớp Division lưu trữ một mảng VPs các phó giám đốc bởi vì bạn chỉ làm việc với một khu vực của công ty. Bây giờ bạn phải làm việc với cả công ty, một khu vực có thể chứa một khu vực con như là những mảng các phó giám đốc VPs. Từ khi cả hai lớp divison và mảng VPs kế thừa từ lớp Corparate, bạn có thể dễ dàng hóan chuyển để lưu trữ và làm việc với đối tượng Corparate trong lớp Division – chú ý rằng hàm print sẽ duyệt qua tất cả các đối tượng trong một khu vực, cho dù chúng là mảng VPs hay khu vực.



Bằng cách chuyển đổi từ việc xử lý lớp VP bên trong một khu vực division sang việc xử lý một lớp Corporate, giờ đây bạn có thể lưu trữ mảng VPs và các khu vực khác – và vì vậy bạn đã hiện thực được mẫu Composite, cho phép bạn có thể xử lý các nút lá hay nhánh cây theo cùng một cách.

Iterator của lớp division, được hiện thực từ lớp DivisionIterator, mã như sau:



Bạn ngả người ra trong ghế với nụ cười hài lòng trên môi, thầm cảm ơn mẫu thiết kế Composite. Để làm một sự chuyển đổi từ ví dụ một khu vực trong phần đầu của chương này, sang một một cấu trúc hình cây cho toàn bộ công ty, tất cả những gì bạn phải làm là bảo đảm rằng tất cả các đối tượng trong cùng một cây phải dựa trên cùng một lớp, và hiện thực cùng một tập hợp các hàm, cho phép chúng được sử dụng theo cùng một cách.

Xây dựng công ty của bạn

Bạn đã có phó giám đốc; bạn đã có các khu vực. Bây giờ là lúc bạn xây dựng một công ty để chứa chúng. Để giữ cho mọi việc đơn giản, bạn có thể sử dụng một ArrayList để lưu trữ các khu vực divisions và các phó giám đốc trong công ty. Tất cả các đối tượng trong công ty đều là đối tượng Corporate, vì vậy ArrayList sẽ lưu trữ các đối tượng Corporate.



Khi bạn muốn thêm một đối tượng Corporate vào cây, chỉ cần sử dụng hàm add của corporate, hàm sẽ thêm một đối tượng mới vào ArrayList.



Muốn in ra thông tin tất cả các đối tượng trong công ty? Chỉ cần gọi hàm print của đối tượng Corporate, khi đó các iterator trong ArrayList sẽ in ra thông tin trong các khu vực và các phó giám đốc của toàn công ty – chú ý rằng khi bạn gọi hàm print của khu vực division, nó sẽ duyệt qua toàn bộ các đối tượng bên trong, và gọi từng hàm print của chúng. Vì vậy khi gọi hàm print từ cấp cao nhất của Corporate, nó sẽ in ra toàn bộ thông tin của công ty.



OK. Đã đến lúc cho chương trình chạy thử. Đầu tiên bạn tạo một đối tượng Corporation



Sau đó bạn tạo khu vực R&D và tạo ra một vài phó giám đốc



Tiếp theo, bạn tạo khu vực Sales. Bạn sử dụng hàm add để thêm không chỉ các phó giám đốc mà còn có thể thêm cả các khu vực con, ví dụ khu vực Western Sales, với một số phó giám đốc



Và bạn có thể thêm các phó giám đốc vào công ty một cách trực tiếp, cũng giống như cách thêm vào một khu vực, bởi vì bạn có thể xử lý các nút lá và các nhánh con theo cùng một cách. Sau khi tạo một phó giám đốc, bạn có thêm phó giám đốc – và khu vực bạn đã tạo trước – vào công ty và in tất cả thông tin chúng ra với một hàm duy nhất là hàm print của đối tượng corporation, hàm này sẽ gọi hàm print của từng phần tử bên trong nó.



Chạy chương trình, và bạn nhận được kết quả.



Bạn đưa danh sách thắng lợi này tới cho vị Giám đốc điều hành. “Đã tới lúc cắt tỉa bớt cái cây này”, bạn nói.

“Hả?” Giám đốc hỏi

“Loại bỏ những cành cây đã chết”, bạn nói. Giám đốc cười hạnh phúc.

End chapter 8: MẪU ITERATOR VÀ MẪU COMPOSITE

Chương 7: TẠO HÀNG LOẠT ĐỐI TƯỢNG VỚI MẪU TEMPLATE (Khuôn Mẫu ) VÀ MẪU BUILDER ( Thợ Xây )

copyright from: congdongcviet.com
Trong chương này, chúng ta sẽ đi qua:
  • Sử dụng mẫu Template
  • Tạo rô bốt sử dụng mẫu template
  • Kế thừa mẫu template
  • Hiểu biết sự khác biệt giữa mẫu Template và mẫu Builder
  • Sử dụng mẫu Builder

“Tin tốt”, giám đốc GigundoCorp – một công ty mới mà bạn đang nhận trách nhiệm tư vấn - nói trong khi chạy vào phòng họp “Chúng ta đã nhận được hợp đồng đó”

“Hợp đồng nào?”, mọi người hỏi

“Hợp đồng về những con rô bốt tự động lắp ráp xe hơi”, vị giám đốc nói.

“Ồ, thì ra là hợp đồng đó” Mọi người nói.

“Giờ thì về phòng và viết chương trình thôi”, vị giám đốc vừa nói và xua đuổi mọi người ra khỏi phòng họp

“Chờ một lát”, bạn nói “Chúng ta có nên dành chút thời gian cho vấn đề thiết kế không? Ví dụ: có khả năng chúng ta sẽ tạo một loại khác của rô bốt trong tương lai chẳng hạn”

“Chắc chắn rồi”, vị giám đốc nói. “Chúng ta có một tá hồ sơ dự thầu ngoài đó. Nhưng không có thời gian nghĩ về nó đâu. Chúng ta cần phải bắt đầu tạo những con rô bốt tự động trước”

“Vâng”, các lập trình viên rên rỉ và mọi người trở về phòng của mình.

“Có điều gì đó mách bảo với tôi rằng họ đang mắc phải sai lầm”, bạn tự nhủ trong căn phòng trống rỗng, rải rác những ly Styrofoam trống rỗng lăn lóc khắp sàn.

Chương này nói này về hai mẫu thiết kế giúp bạn có một cách thức khéo léo hơn trong việc tạo dựng các đối tượng: mẫu Template Method và mẫu Builder. Mẫu Template Method cho phép các lớp con định nghĩa lại các bước tạo đối tượng, rất thích hợp cho việc tạo ra các chủng loại rô bốt khác nhau. Mẫu Builder giúp bạn uyển chuyển hơn trong việc đối tượng vì nó tách rời quá trình khởi tạo ra khỏi bản thân đối tượng. Cả hai mẫu sẽ được thảo luận trong chương này

Tạo con rô bốt đầu tiên

Các lập trình viên của GigundoCorp đã xào nấu ra phần mềm của họ trong vài ngày và nó vừa đủ đơn giản. Lớp robot bắt đầu với một hàm khởi tạo như sau:



Và có một số hành động mà robot có thể thực hiện, ví dụ như, để khởi động robot, bạn gọi hàm bắt đầu Start, để robot làm việc, bạn gọi hàm lắp ráp assemble, để kiểm tra sản phẩm, bạn gọi hàm kiểm tra test, và vân vân.



Và tất cả những gì bạn cần là một phương thức, tên là go here, nó sẽ làm cho robot làm việc bằng cách gọi các hàm start, getParts, assemble, test và stop như sau:



Bạn có thể nhanh chóng viết chương trình kiểm tra. Đầu tiên tạo một robot và gọi hàm go như sau:



Và khi chạy chương trình, bạn nhận được kết quả:



“Tuyệt vời”, giám đốc điều hành phấn khích. “Phần thưởng luôn ở xung quanh. Tôi đã nói với anh rằng họ không cần cái thứ mẫu thiết kế vớ vấn đó”. Các lập trình viên của công ty ném cho bạn một ánh nhìn dè bỉu.

Tạo Robot với Mẫu thiết kế Template Method

Ngày tiếp theo, “Tin tốt”, giám đốc điều hành của GigundoCorp la lớn, trong khi phóng vào phòng họp. “Chúng ta kí được hợp đồng khác!”

“Hợp đồng khác nào?” Mọi người hỏi

“Hợp đồng cho robot nướng bánh” Vị giám đốc nói “Giờ thì ra khỏi đây và viết phần mềm cho nó”

Các lập trình viên nhìn vào trong ly cà phê của họ “Chúng ta phải viết lại tất cả phần mềm từ đầu”, họ nói

Vị giám đốc liếc mắt nhìn bạn và hỏi “Có tốn nhiều chi phí không?”

“Rất nhiều”, các lập trình viên nói. Và bạn thì đang chống lại sự thúc giục để nói rằng “Tôi đã nói với các anh từ trước”

Đây là thời điểm thích hợp để nói về mẫu thiết kế Template Method. Có một rắc rối mà lập trình viên GigundoCopr đối mặt, họ có một con robot tự động như hình sau:



Nhưng bây giờ họ cần một con robot nướng bánh như hình sau, và thế là phải viết lại mã nguồn từ đầu



Con robot nướng bánh có một số chức năng giống như con robot lắp ráp ô tô, như là hàm start, stop, tuy nhiên nó có những sự khác biệt như lắp ráp assemble sẽ không hiển thị “Getting a carburetor” mà thay vào đó là “Getting flour and sugar…”

Đó là nơi mà mẫu thiết kế Template Method được áp dụng. Mẫu này nói rằng, bạn có thể viết một phương thức, dùng để xác định một loạt các thuật toán, giống như hàm go mà bạn thấy trước đây, để chạy một loạt các chức năng cho robot như hình:



Sau đó bạn đưa hàm này vào một bộ khuôn template bằng cách cho phép các lớp con định nghĩa lại các bước thuật toán theo cách cần thiết. Trong trường hợp này, để làm một con robot nướng bánh, bạn sẽ viết lại các hàm getParts, assemble, và test.

Theo định nghĩa chính thức của sách GoF, mẫu Template Method như sau: “Định nghĩa một bộ khung của một thuật toán trong một chức năng, chuyển giao việc thực hiện nó cho các lớp con. Mẫu Template Method cho phép lớp con định nghĩa lại cách thực hiện của một thuật toán, mà không phải thay đổi cấu trúc thuật toán.”

Điều này có nghĩa là bạn nên sử dụng mẫu Template Method khi bạn có một thuận toán được tạo bởi nhiều bước, và bạn muốn thể tùy chỉnh một số bước trong đó. Chú ý rằng nếu bạn muốn viết lại mọi thứ từ đầu – khi mọi bước đều phải tùy chỉnh lại – thì bạn không cần dùng template.

Tạo robot bằng bộ khuôn Template

Nếu bạn có một bộ khuôn Template dựa trên robot, bạn có thể cho nó kế thừa như hình sau:



Bằng cách gọi hàm go, tập hợp các thuật toán sẽ được thực hiện. Để tùy chỉnh trong lớp kế thừa, bạn chỉ cần viết lại một số bước nào bạn muốn, trong trường hợp robot nướng bánh sẽ như hình sau:



Đó là ý tưởng đằng sau mẫu thiết kế Template Method – Một chức năng bao gồm nhiều bước sẽ được tùy chỉnh bởi lớp con. Trong trường hợp bạn cần hai robot, một robot lắp ráp ô tô, một robot nướng bánh, mọi việc sẽ như thế nào?

Bạn bắt đầu bằng cách tạo một bộ khuôn Template trong một lớp trừu tượng abstract ( để lớp khác có thể kế thừa nó), gọi là RobotTemplate



Và lớp này cũng cài đặt việc thực hiện mặc định cho từng chức năng trong hàm algorithm, start, getParts, assemble, test và stop.



Nếu một con robot sử dụng đúng các phương thức này, ví dụ như hàm start và stop, chúng ta không cần phải viết lại chúng. Ngược lại bạn có thể thay đổi các phương thức này trong các lớp con.

Ví dụ, bạn có thể sử dụng RobotTemplate để tạo một con robot lắp ráp ô tô. Bạn có thừa kế từ lớp trừu tượng RobotTemplate trong một lớp mới, lớp AutomotiveRobot.

Code:
public class AutomotiveRobot extends RobotTemplate
{
...

}
Robot lắp ráp ô tô này phải viết lại một số hàm của RobotTemplate như hàm getParts sẽ thông báo “Getting a carburetor…”, hàm assemble sẽ thông báo “Installing the carburetor…”, và hàm test sẽ thông báo “Revving the engine…”. Bạn thấy đó, bạn có thể tùy chỉnh các bước trong một thuật toán được cung cấp bởi một bộ khuôn template.



Bạn cũng có thể tùy chỉnh mã nguồn dựa trên template bằng cách thêm vào một số hàm, ví dụ như hàm khởi tạo sẽ nhận tên của con robot, và hàm getName sẽ trả về tên này.



Tuyệt vời. Bạn đã kế thừa phương thức go từ template, và tùy chỉnh nó cho robot lắp ráp ô tô.

Bạn cũng có thể tùy chỉnh hàm go kế thừa từ template, trong trường hợp tạo robot nướng bánh. Bạn tạo lớp mới CookieRobot, kế thừa từ lớp RobotTemplate. Bạn có thể viết lớp CookieRobot bằng cách làm cho hàm getParts thông báo “Getting flour and sugar…”, hàm assemble thông báo “Baking a cookie…”, và hàm test thông báo “Crunching a cookie…”



Tới giờ, bạn đã sử dụng hàm go từ bộ khuôn template để tạo hai lớp mới, AutomotiveRobot và CookieRobot, và bạn đã viết lại một số bước trong thuật toán tùy thuộc vào hai loại robot khác nhau. Bạn đã không phải viết lại hai lớp này từ đầu.

Kiểm tra việc tạo Robot

Bạn hãy tạo hai đối tượng của hai lớp AutomotiveRobot và CookieRobot, và gọi hàm go như sau:



Khi bạn chạy thử chương trình, bạn có thể thấy rằng bạn thật sự có thể tùy chỉnh một số bước trong thuật toán của hai loại robot khác nhau.



Thêm vào một “hook” (ND: Hook – móc câu – một kỹ thuật chặn bắt chương trình)

Bạn cũng có thể thêm vào một hook trong thuật toán. Một hook là phương pháp kiểm soát một số khía cạnh của thuật toán. Ví dụ, nếu bạn muốn phần kiểm tra testing trong thuật toán Robot có thể thực hiện hay không, bạn có thể thêm vào một điều kiện, một hàm hook có tên testOK như sau:



Mặc định, bạn có thể bỏ qua hàm hook testOK – nếu không làm gì khác, thuật toán Robot sẽ gọi đầy đủ các bước, bao gồm cả hàm test. Tuy nhiên bạn có thể “câu móc” vào thuật toán bằng cách viết lại hàm testOK trong một lớp con, lớp CookieHookRobot, nơi mà hàm testOK sẽ trả về giá trị false, không phải là true.



Bởi vì hàm hook testOK trả về giá trị false, thuật toán Robot sẽ không gọi hàm test từ hàm go, bạn có thể xem mã sau:



Kiểm tra hàm hook:

Bây giờ tạo chương trình, và gọi hàm cookieHookRobot.go:



Bạn sẽ thấy thuật toán Robot thực hiện, trừ bước test:



Bạn thấy đó, bạn đã không phải làm bất cứ thứ gì với hàm hook, tuy nhiên nếu bạn muốn, bạn có thể tác động lên việc thực hiện của thuật toán. Nếu bạn xây dựng một thuật toán sử dụng nhiều hàm trừu tượng, từng hàm này sẽ được viết lại ở lớp con, mặc khác, hàm hook sẽ không phải viết lại, trừ khi bạn muốn thay đổi việc thực thi mặc định của thuật toán.

Bạn sử dụng mẫu thiết kế Template Method khi bạn có một thuật toán với nhiều bước và bạn muốn cho phép tùy chỉnh chúng trong lớp con. Thật dễ dàng. Bằng cách viết lại các hàm đã được khai báo trong lớp trừu tượng, bạn sẽ thay đổi được theo cách bạn muốn.

Mẫu thiết kế Template Method là một ý tưởng tuyệt vời khi bạn có một thuật toán nhiều bước mà chính bạn có thể tùy chỉnh nó. Có một mẫu thiết kế khác cũng làm việc giống vậy, mà tôi sẽ thảo luận trong phần tới của chương, nó là mẫu Builder.

Xây dựng Robots với mẫu Builder

“Tin tốt!” Giám đốc điều hành của GigundoCorp reo lên, trong khi phóng như bay vào phòng họp. “Khách hàng của chúng ta nói rằng họ muốn kiểm soát nhiều hơn tính năng của Robot, vì vậy chúng ta không thể sử dụng những bộ khuôn Template đã viết sẵn được nữa. Bây giờ họ muốn họ có thể chọn hành động mà robot sẽ thực hiện”

“Để tôi làm rõ chỗ này”, bạn nói “Đầu tiên, chúng ta thiết lập mọi thứ , robot khởi động, nhận nguyên liệu, lắp ráp, kiểm tra và dừng. Nhưng bây giờ khách hàng lại muốn kiểm soát trình tự này và chọn lựa những chức năng họ muốn? Có thể là robot khởi động, rồi kiểm tra, rồi lắp ráp, rồi dừng?”

“Đúng vậy”, Giám đốc nói

“Đây là thời điểm để sử dụng một mẫu thiết kế mới”, bạn nói

“Tôi e rằng phải làm như vậy”, Giám đốc nói.

Những quy định của khách hàng

Trong mẫu thiết kế Template Method, vấn đề chính là những thuật toán nhiều bước – bạn có thể cài đặt nó theo cách bạn muốn, và những lớp con sử dụng theo cách bạn đã thiết lập (Mặc dù bạn có thể viết lại một số bước, nhưng quy trình vẫn không thay đổi ). Nhưng bây giờ tình hình đã khác – khách hàng muốn họ thiết lập trình tự hoạt động và số lượng các bước của thuật toán. Vì vậy mã nguồn mà bạn đã phát triển không còn là trung tâm chính nữa, bạn phải đóng gói nó trong một lớp mới, lớp builder.

Mẫu Template Method mà ta đã được làm quen trong phần trước cho phép bạn tùy chỉnh các bước của một thuật toán bằng cách viết lại các bước trong thuật toán như hình sau:



Mọi chức năng đều dựa trên khuôn mẫu Template trong mẫu thiết kế này, và bạn có thể tùy chỉnh template theo cách bạn muốn. Nhưng bây giờ bạn không còn điều khiển thuật toán nữa, thay vào đó chính khách hàng thực hiện. Họ tạo robot với những chức năng và trình tự họ muốn. Ví dụ để thêm hành động khởi động, khách hàng có thể gọi hàm addStart. Để thêm hành động kiểm tra, họ gọi hàm addTest và vân vân. Hình minh họa như sau:



Để có thể đáp ứng yêu cầu kiếm soát hành động robot của khách hàng GigundoCorp, bạn phải chuyển mã nguồn cũ qua một lớp mới, lớp CookieRobotBuilder, lớp này hỗ trợ các hàm addStart, addTest, addAssemble và hàm addStop, như hình sau:



Nhờ đó, khách hàng có thể sử dụng CookieRobotBuilder để tạo robot nướng bánh. Khi khách hàng tạo xong robot, mã nguồn sẽ gọi hàm getRobot của đối tượng CookieRobotBuilder để nhận về một robot mới, như hình vẽ sau:



Và bây giờ khác hàng đã nắm quyền kiểm soát các thuật toán, bạn không phải kế thừa một template mẫu nữa.Thay vì vậy, để tạo một loại khác của robot, bạn cho phép khách hàng sử dụng những đối tượng builder khác nhau.

Ý tưởng chính như sau: bây giờ khách hàng có thể thiết lập trình tự và số lượng các bước trong thuật toán, và chọn lựa đúng đối tượng builder để tạo ra robot mà họ muốn.
Sách của GoF nói rằng, mẫu thiết kế Builder “Tách rời việc tạo dựng một đối tượng phức tạp ra khỏi bản thân đối tượng vì vậy cho phép cùng một quá trình tạo dựng có thể tạo ra nhiều loại đối tượng khác nhau”

Khác biệt lớn nhất giữa mẫu Template Method và mẫu Builder là ai sẽ tạo ra trình tự các bước trong thuật toán. Trong mẫu Template, bạn là người tạo ra trình tự, và các lớp con sẽ hiện thực chúng. Trong mẫu Builder, khách hàng sẽ thiết lập trình tự và số lượng các bước trong thuật toán, và hoán đổi giữa các builder mà phải cung cấp để tạo ra các đối tượng khác nhau thể hiện thuật toán đó.

Sử dụng mẫu thiết kế Builder khi bạn muốn khách hàng kiểm soát được quá trình tạo dựng. Ví dụ, đây là mẫu thiết kế mà bạn muốn khi bạn xây dựng robot sử dụng cùng một quá trình khởi tạo nhưng muốn có thể tạo ra những con robot khác nhau. Tất cả những gì khách hàng cần là gọi những builder khác nhau – quá trình xây dựng vẫn như cũ. Đây là một ví dụ khác, bạn muốn đọc một đoạn văn bản và xây dựng một tài liệu, nhưng bạn lại không biết định dạng chính xác của nó là RTF, Microsoft Word, hay văn bản đơn giản… Mặc dù quá trình tạo dựng là giống nhau cho từng tài liệu, bạn có thể sử dụng những builder khác nhau để tạo dựa vào kiểu của loại tài liệu.

Nói cách khác, khi khách hàng muốn kiểm soát quá trình tạo dựng, nhưng bạn vẫn muốn có thể tạo ra nhiều đối tượng khác nhau, mẫu Builder sẽ giúp bạn thực hiện điều đó.

Ghi nhớ: Mẫu thiết kế này tương tự với mẫu Factory, nhưng mẫu Factory là trung tâm trong quá trình khởi tạo một bước, chứ không cài đặt nhiều bước như ở đây.

Cho phép khách hàng tạo Robot

Khi bạn sử dụng mẫu Builder, khách hàng sẽ phụ trách quá trình tạo dựng. Khách hàng sử dụng đối tượng xây dựng builder của bạn để làm những gì họ muốn. Để cho phép khách hàng tạo robot thể hiện một loạt các hành động – khởi động, lắp ráp, ngừng… - Tôi tạo ra một giao diện interface Robot Builder hỗ trợ các hàm như sau: addStart, addGetParts, addAssemble, addTest và addStop:

Ví dụ để tạo một robot với các hành động start, test, assemble và sau đó là stop, khách hàng chỉ cần gọi hàm addStart, addTest, addAssemble và addStop của đối tượng xây dựng builder theo đúng trình tự đó. Khi robot đã được tạo xong, khách hàng chỉ cần gọi hàm getRobot của Builder để nhận về một robot mới. Và đối tượng robot mới này có hỗ trợ hàm go, hàm này sẽ thực hiện hàng loạt hành động mà bạn đã tạo dựng trước đó.

Bởi vì bạn có nhiều loại đối tượng builder để tạo nhiều loại robot khác nhau – ví dụ như builder xây dựng robot làm bánh, builder xây dựng robot lắp ráp ô tô – Tôi sẽ tạo một giao diện interface RobotBuilder mà tất cả các builder sẽ hiện thực giao diện này. Đây là những hàm mà các robot builder phải hiện thực, từ hàm addStart tới hàm addStop, kể cả hàm getRobot. Xem mã sau:



Tôi bắt đầu tạo builder cho robot làm bánh, CookieRobotBuilder, cũng giống như tất cả các builder khác, builder này cần phải hiện thực giao diện RobotBuilder



Đối tượng robot trong mã nguồn trên, dựa trên lớp CookieRobotBuildable, sẽ được nói tới trong phần “Tạo một vài robot tương thích”. Đối tượng robot được tạo sẽ là một đối tượng CookieRobotBuildalbe. Vì thế chúng cần cần một biến để lưu trữ đối tượng này, mã như sau:



Khách hàng có thể cài đặt các hành động cho robot như start, stop, test, assemble, getParts … theo trình tự bất kì. Để lưu lại trình tự này, tôi sử dụng kiểu ArrayList, với đối tượng actions như sau:



Cách dễ dàng nhất để lưu trữ trình tự các hành động của robot trong mảng danh sách actions là gán từng giá trị số nguyên cho từng hành động, như hình sau:



Tôi lưu đối tượng số nguyên trong một mảng danh sách ArrayList. Ví dụ, khi khách hàng muốn thêm hành động start, chương trình gọi hàm addStart, và robot builder sẽ thêm một đối tượng số nguyên có giá trị 1 vào mảng danh sách actions, và cứ thế tiếp tục… Đây là tất cả các hàm để thêm chức năng cho robot trong builder:



Khi khách hàng muốn tạo một đối tượng robot, họ sẽ gọi hàm getRobot của builder này. Khi hàm này được gọi, bạn biết rằng quá trình khởi tạo đã hoàn tất, vì vậy bạn có thể cài đặt robot bằng cách chuyển giao cho nó tham số mảng danh sách actions mà nó sẽ thực thi. Trong ví dụ này, từng robot sẽ được cài đặt bằng cách chuyển tham số actions thông qua hàm loadActions. Mã như sau:



Vậy là hoàn thành phần đối tượng xây dựng builder, nó cho phép khách hàng tự cài đặt robot theo trình tự họ muốn. Vậy làm sao để tạo lớp Robot mà ta đã sử dụng ở trên?

Tạo một số robot thích hợp:

Từng loại thợ xây builder sẽ tạo ra một loại robot khác nhau, từng robot lại được tạo từ lớp cơ sở của nó, như lớp CookieRobotBuildable hay AutomotiveRobotBuildalble. Tất cả các robot phải có cùng một hàm go để thực hiện các chức năng. Vì vậy bạn có tạo một giao diện interface với tên RobotBuildable để chắc chắn rằng mọi robot đều phải hiện thực giao diện này. Mã như sau:



Bây giờ tất cả Robot đều phải hiện thực giao diện này. Đây là cách lớp RobotBuildable hoạt động. Bạn có thể nạp robot với mảng danh sách actions, thông qua hàm loadActions, với tham số actions đã được đối tượng builder tạo trước. Xem mã sau:



Khi khách hàng muốn robot thực hiện các hành động được cài đặt sẵn, họ gọi hàm go. Trong hàm go, bạn có thể duyệt qua mảng actions và gọi từng hàm tương ứng với chức năng đó. Ví dụ bạn duyệt qua đối tượng số nguyên “1” trong actions, bạn sẽ gọi hàm start, duyệt tới số “2” bạn gọi hàm getParts và vân vân..Bạn có thể sử dụng một đối tượng Iterator và phát biểu switch trong hàm go như sau:



Ghi chú: Bạn cũng cần phải thêm các hàm cho từng hành động như : hàm start ( hiển thị chữ “Starting…”), hàm getParts ( hiển thị chữ Getting flour and sugar… ) vân vân.



Vậy là hoàn tất lớp CookieRobotBuilable. Giờ bạn đã có đối tượng xây dựng Builder và robot. Giờ là lúc để thử nghiệm chúng



Tùy thuộc vào loại robot mà user chọn lựa, đối tượng builder dành cho robot làm bánh hay builder dành cho robot lắp ráp ô tô sẽ được tạo ra. Xem mã sau:



Sau đó khách hàng tạo loại robot mà họ muốn, và sử dụng các hàm addStart, addGetParts, addAssemble, addTest và addStop theo trình tự họ muốn.



Sau khi robot được tạo, khách hàng gọi hàm getRobot, đối tượng robot trả về được lưu trong biến RobotBuildable. Và bạn có thể gọi hàm go của robot. Mã như sau:



Khách hàng có thể tạo robot làm bánh hay robot lắp ráp ô tô một cách đơn giản thông qua việc chọn đúng builder. Đây là kết quả:



Và đây là kết qủa việc tạo robot lắp ráp ôtô, sử dụng cùng một quy trình khởi tạo:



Tuyệt vời. Bạn có thể đưa builder cho khách hàng, giúp khách hàng có thể kiểm soát quá trình tạo dựng đối tượng.