Design pattern - Kết hợp design pattern (P1)

Kết hợp design pattern – Mẫu của các mẫu (P1)

Kết hợp design pattern

Có ai đoán rằng các mẫu thiết kế có thể làm việc với nhau? Chà, tin hay không, một số thiết kế OO linh hoạt nhất sẽ kết hợp các design pattern lại với nhau. Hãy sẵn sàng để đưa các kỹ năng design pattern của bạn lên cấp độ tiếp theo; đây là thời gian cho các mẫu được “hợp nhất”.

Kết hợp design pattern

Một trong những cách tốt nhất để sử dụng các mẫu là để chúng có thể tương tác với các mẫu khác. Bạn càng sử dụng các mẫu, bạn sẽ càng thấy chúng có mặt cùng nhau trong các thiết kế của bạn. Chúng tôi có một tên đặc biệt cho một tập hợp các mẫu làm việc cùng nhau trong một thiết kế có thể được áp dụng qua nhiều vấn đề: mẫu kết hợp (compound pattern). Đúng vậy, bây giờ chúng ta đang nói về mẫu được tạo nên từ các mẫu khác!

Bạn sẽ tìm thấy rất nhiều “mẫu kết hợp” được sử dụng trong thế giới thực. Bây giờ bạn đã có khái niệm từng mẫu trong đầu, bạn sẽ thấy rằng chúng thực sự chỉ là các mẫu hoạt động cùng nhau, và điều đó làm cho chúng dễ hiểu hơn.

Chúng tôi sẽ bắt đầu chương này bằng cách xem lại những con vịt thân thiện của chúng tôi trong trình giả lập vịt SimUDuck. Rốt cuộc, chúng đã đồng hành cùng chúng tôi trong toàn bộ cuốn sách và chúng đã thể hiện rất tốt về việc tham gia vào rất nhiều mẫu thiết kế. Những con vịt sẽ giúp bạn hiểu làm thế nào các mẫu có thể làm việc cùng nhau trong cùng một giải pháp.

Nhưng chỉ vì chúng tôi sẽ kết hợp một số mẫu với nhau, không có nghĩa là chúng tôi có một giải pháp đủ điều kiện là một “mẫu tổng hợp”. Đừng cho rằng nó phải là một giải pháp chung có thể được áp dụng cho tất cả vấn đề. Vì vậy, trong nửa sau của chương, chúng ta sẽ ghé thăm một mẫu kết hợp “thực sự” khác: đúng không, anh Model-View-Controller. Nếu bạn đã nghe nói về anh ấy, bạn sẽ thấy mẫu kết hợp này là một trong những mẫu mạnh nhất trong “hộp công cụ thiết kế” của bạn.

Các mẫu thường được sử dụng cùng nhau và kết hợp trong cùng một giải pháp thiết kế.

Một mẫu kết hợp hai hoặc nhiều mẫu thành một giải pháp giải quyết vấn đề được lặp lại hoặc vấn đề chung.

Cuộc hội ngộ những con vịt

Như bạn vừa nghe, chúng ta sẽ làm việc với những con vịt một lần nữa. Lần này những con vịt sẽ cho bạn thấy làm thế nào các mẫu có thể cùng tồn tại và thậm chí hợp tác trong cùng một giải pháp.

Chúng ta sẽ xây dựng lại trò chơi mô phỏng vịt ở đầu quyển sách và cung cấp cho nó một số khả năng thú vị bằng cách sử dụng một loạt các mẫu. Được rồi, hãy bắt đầu…

1. Đầu tiên, chúng tôi sẽ tạo ra một giao diện Quackable

Giống như chúng tôi đã nói, sẽ bắt đầu lại từ đầu. Lần này, Vịt (Duck) sẽ implements interface Quackable. Bằng cách đó, chúng tôi sẽ biết những thứ trong trình giả lập có thể kêu quack() – như loại vịt Mallard DucksRedhead DucksDuck Calls và thậm chí chúng ta có thể thấy Vịt Cao su (Rubber Duck) kêu quack quack. 

Quackable interface

2. Bây giờ, một số loại Vịt sẽ implements Quackable

Một giao diện tốt mà không có một số lớp implement nó thì cũng bỏ đi? Đã đến lúc tạo ra một vài con vịt kế thừa (concrete ducks).

Sẽ không vui nếu chúng ta không thêm các loại Vịt khác vào.

Duck Call - Hand-crafted Wooden Whistle

Duck Call

Nhớ lần trước không? Chúng ta đã có duck calls (còi giả tiếng vịt – những thứ mà thợ săn sử dụng, chúng chắc chắn có thể kế thừa Quackable) và vịt cao su (rubber ducks).

Chúng vẫn kêu nhưng không giống tiếng vịt thật.

3. Được rồi, chúng ta đã có những con vịt; bây giờ tất cả những gì chúng ta cần là một trình giả lập (Simulator)

Hãy tạo một trình giả lập sau đó tạo ra một vài con vịt và đảm bảo các phương thức quack() của chúng ta đang làm việc…

Tất cả đều implements cùng một giao diện Quackable, nhưng việc implements của chúng cho phép thực hiện theo những cách riêng.

Có vẻ như mọi thứ đang hoạt động; càng về sau càng tốt.

4. Khi vịt đã implement, nhưng ngỗng không thể

Ở đây, một lớp Ngỗng (Goose) đã được code sẵn như thế này:

Một con ngỗng sẽ kêu “honk”, không phải kêu “quack”

SỬ DỤNG SỨC MẠNH BỘ NÃO

Giả sử chúng tôi muốn có thể sử dụng Ngỗng ở bất kỳ đâu chúng tôi muốn sử dụng Vịt. Rốt cuộc, ngỗng cũng làm ồn; ngỗng bay; ngỗng bơi. Tại sao chúng ta không thể có Ngỗng trong trình mô phỏng?

Mẫu thiết kế nào sẽ cho phép Ngỗng dễ dàng xen kẽ với Vịt? Nói cách khác, mẫu nào sẽ cho phép Ngỗng có thể implements Quackable?

5. Chúng tôi cần một bộ chuyển đổi ngỗng (goose adapter)

Simulator của chúng tôi mong đợi sẽ thấy các giao diện Quackable. Vì ngỗng không thể kêu quack quack (chúng kêu honk), chúng tôi có thể sử dụng một bộ chuyển đổi (adapter) để điều chỉnh một con ngỗng thành một con vịt.

6. Bây giờ ngỗng cũng có thể được tạo trong trình giả lập

Tất cả những gì chúng ta cần làm là tạo ra một con Ngỗng, bọc nó trong một Adapter (thứ sẽ implements Quackable).

7. Bây giờ hãy chạy thử

Lần này khi chúng ta chạy trình giả lập, danh sách các đối tượng được truyền cho phương thức simulate() bao gồm một ngỗng (Goose) được “bọc” trong một bộ chuyển đổi (GooseAdapter). Kết quả thế nào? Chúng ta sẽ thấy một số tiếng kêu của ngỗng: honk…honk…!

NGHIÊN CỨU TIẾNG KÊU VỊT

Những nhà nghiên cứu bị “mê hoặc” bởi tiếng vịt kêu. Một điều mà các nhà nghiên cứu luôn muốn là đếm tổng số tiếng quack được tạo ra bởi một đàn vịt.

Làm thế nào chúng ta có thể thêm khả năng đếm tiếng quack mà không phải thay đổi các lớp vịt?

Bạn có thể nghĩ ra một mẫu nào đó sẽ giúp chúng ta?

8. Chúng tôi sẽ làm cho những nhà nghiên cứu vui vẻ và đếm tiếng kêu quack cho họ

Làm sao ư? Hãy tạo ra một decorator cho vịt một số hành vi mới (hành vi đếm) bằng cách bọc chúng bằng một decorator object. Chúng ta sẽ không cần thay đổi mã nguồn của lớp Duck.

9. Chúng ta cần cập nhật trình giả lập để tạo ra những con vịt được trang trí

Bây giờ, chúng ta phải bọc từng đối tượng Quackable mà chúng ta khởi tạo bằng QuackCounter decorator. Nếu chúng ta không làm như vậy, chúng ta sẽ có những con vịt chạy xung quanh mà không đếm được.

BẠN PHẢI CÓ NHỮNG DECORATE OBJECT ĐỂ TRANG TRÍ CÁC HÀNH VI

Anh ấy nói đúng, đó là vấn đề với việc bọc đối tượng: bạn phải chắc chắn rằng Vịt phải được bao bọc bởi các decorator, nếu không chúng sẽ không được trang trí (và khi đó chúng không được đếm).

Tại sao chúng ta không đưa việc tạo những con vịt ra một nơi khác? nói cách khác, hãy tách công việc tạo và trang trí vịt ra và đóng gói nó.

Điều đó nghe trông giống như mẫu nào?

10. Chúng tôi cần một nhà máy để sản xuất vịt!

Được rồi, chúng tôi cần kiểm soát chất lượng để đảm bảo rằng vịt của chúng tôi sẽ được bọc. Chúng tôi sẽ xây dựng toàn bộ nhà máy chỉ để sản xuất chúng. Nhà máy nên sản xuất một bộ các sản phẩm bao gồm các loại vịt khác nhau, vì vậy chúng tôi sẽ sử dụng Abstract Factory Pattern.

Hãy bắt đầu với định nghĩa của AbstractDuckFactory:

Hãy bắt đầu bằng cách tạo ra một nhà máy tạo ra những con vịt không có decorator, mục đích chỉ để học cách tạo một Factory:

Bây giờ, hãy cùng nhau tạo ra nhà máy mà chúng ta thực sự muốn, CountingDuckFactory:

11. Hãy thiết lập Simulator để sử dụng nhà máy (factory)

Hãy nhớ làm thế nào Abstract Factory hoạt động? Chúng tôi tạo ra một phương thức đa hình (polymorphic method) lấy một nhà máy (factory) và sử dụng nó để tạo các đối tượng. Bằng cách thông qua các factory khác nhau, chúng tôi có thể sử dụng các bộ sản phẩm khác nhau trong phương thức.

Chúng tôi sẽ thay đổi phương thức simulate() để nó truyền vào một nhà máy và sử dụng nó để tạo ra vịt.

Đây là kết quả sau khi sử dụng factory

Giống như lần trước, nhưng lần này chúng tôi đảm bảo rằng tất cả các con vịt đều được trang trí bởi vì chúng tôi đang sử dụng CountingDuckFactory

GỌT BÚT CHÌ CỦA BẠN

Chúng ta vẫn trực tiếp khởi tạo ngỗng bằng cách dựa vào các lớp con (concrete classes) . Bạn có thể viết một Abstract Factory cho ngỗng không? Chúng xử lí việc tạo ra “goose ducks” bằng cách nào?

Ranger Brewer

Ah, anh ấy muốn quản lý một đàn vịt.

Ở đây, một câu hỏi hay khác từ Ranger Brewer: Tại sao chúng ta quản lý vịt riêng lẻ?

Những gì chúng ta cần là một cách để nói về một “tập hợp”  vịt và thậm chí cả “tập hợp con” của vịt (để giải quyết yêu cầu từ Ranger Brewer). Sẽ thật tuyệt nếu chúng ta có thể áp dụng các hoạt động trên toàn bộ đàn vịt.

Mẫu thiết kế nào có thể giúp chúng ta?

12. Hãy tạo ra một đàn vịt (tốt, thực sự là một tập hợp Quackable)

Bạn có nhớ Composite Pattern cho phép chúng ta xử lý một “bộ” các đối tượng theo cùng một cách như xử lý với các đối tượng riêng lẻ không? Hãy xem qua cách thức hoạt động của nó:

Bạn có để ý rằng chúng tôi đã “lén lút” đặt một Mẫu thiết kế vào code của bạn mà không đề cập đến nó không? (Iterator pattern)

13. Bây giờ chúng ta cần thay đổi trình giả lập

Composite của chúng tôi đã sẵn sàng; chúng ta chỉ cần một số đoạn code để hoàn chỉnh các con vịt thành cấu trúc composite.

Đây là kết quả:

An toàn vs Minh bạch (Safety vs Transparency)

Bạn có thể nhớ rằng trong chương Composite Pattern, các composite (Menu) và các nút lá (MenuItems) có cùng một bộ phương thức giống nhau, bao gồm phương thức add(). Vì chúng có cùng một bộ phương thức, chúng tôi vẫn có thể gọi các phương thức trên MenuItems nhưng chúng không thực sự có ý nghĩa (như cố gắng thêm một cái gì đó vào MenuItem bằng cách gọi add()). Lợi ích của việc này là sự khác biệt giữa nút lá và composite là trong suốt (transparency): client không phải biết liệu nó đang xử lý một nút lá hay một composite; nó chỉ được gọi cùng một phương thức trên cả hai.

Ở đây, chúng tôi đã quyết định giữ các phương thức bảo trì con của composite  tách biệt với các nút lá: đó là, chỉ những Flock mới có phương thức add(). Chúng tôi biết sẽ không có ý nghĩa gì khi cố gắng thêm một cái gì đó vào Duck (một con vịt không thể add một con vịt khác) và trong triển khai này, bạn không thể. Bạn chỉ có thể add() vịt vào một bầy (Flock). Vì vậy, thiết kế này an toàn hơn (Safety) – bạn không thể gọi các phương thức không có ý nghĩa đối với các component – nhưng nó kém minh bạch (transparent) hơn. Bây giờ client cần phải biết rằng khi Quackable là một Flock thì mới có thể thêm Quackables vào nó.

Như mọi khi, có sự đánh đổi khi bạn thiết kế OO và bạn cần xem xét chúng khi bạn tạo các composite của riêng bạn.

Bạn có thể nói “Observer?”

Nghe có vẻ như nhà nghiên cứu muốn quan sát hành vi của từng con vịt. Điều đó dẫn chúng ta đến một mẫu được thực hiện để quan sát hành vi của các đối tượng: Mẫu quan sát (Observer Pattern).

14. Trước tiên chúng ta cần một Observable interface

Hãy nhớ rằng một Observable là một đối tượng được quan sát. Một Observable cần phương thức để đăng ký và thông báo tới người quan sát (observer). Chúng tôi cũng có một phương thức để loại bỏ các quan sát viên (removing observer), nhưng chúng tôi sẽ giữ cho việc thực hiện đơn giản ở đây và chúng tôi sẽ không cài đặt phương thức removing observer.

Bây giờ chúng tôi cần đảm bảo tất cả các Quackable implements giao diện này…

15. Bây giờ, chúng ta cần đảm bảo rằng tất cả các lớp cụ thể triển khai Quackable có thể xử lý là một QuackObservable

Chúng ta có thể tiếp cận điều này bằng cách thực hiện đăng ký và thông báo trong mỗi và mọi lớp (giống như chúng ta đã làm trong Chương 2). Nhưng lần này chúng tôi sẽ làm điều đó một chút khác biệt: chúng tôi sẽ đóng gói code đăng ký và thông báo trong một lớp khác, gọi nó là Observable và kết hợp (compose) nó với QuackObservable. Bằng cách đó, chúng tôi chỉ viết code thực một lần và QuackObservable chỉ cần đủ code để ủy quyền cho lớp helper QuackObservable.

Hãy bắt đầu với lớp helper QuackObservable

16. Tích hợp lớp helper Observable với các lớp Quackable

Điều này không nên quá tệ. Tất cả những gì chúng ta cần làm là đảm bảo rằng các lớp Quackable được cấu thành với một Observable và chúng biết cách ủy thác cho nó. Sau đó, chúng đã sẵn sàng để trở thành người quan sát (observer). Dưới đây là triển khai MallardDuck; Những con vịt khác cũng vậy.

LÀM NHỌN BÚT CHÌ CỦA BẠN

Chúng tôi đã không thay đổi việc implement một Quackable, QuackCounter decorator. Chúng ta cũng cần phải làm cho nó một Observable. Tại sao bạn không viết nó.

17. Chúng ta làm gần xong rồi! Chỉ cần code thêm phía (Observer) của mẫu

Chúng tôi đã triển khai mọi thứ chúng tôi cần cho Observable; bây giờ chúng ta cần một số người quan sát (Observer). Chúng tôi sẽ bắt đầu với giao diện Observer:

Bây giờ chúng ta cần một Người quan sát: những người nhà nghiên cứu đó đang ở đâu?!

LÀM NHỌN BÚT CHÌ CỦA BẠN

Điều gì sẽ xảy ra nếu một nhà nghiên cứu muốn quan sát cả một đàn (Flock)? Điều đó có nghĩa là gì? Hãy nghĩ về nó như thế này: nếu chúng ta quan sát một composite, thì chúng ta sẽ quan sát mọi thứ trong composite đó. Vì vậy, khi bạn đăng ký với một đàn (Flock), flock composite đảm bảo bạn được đăng ký với tất cả các con của nó (xin lỗi, tất cả các quackers nhỏ của nó), có thể bao gồm các Flock khác.

Hãy tiếp tục và viết code Flock observer trước khi chúng ta tiến xa hơn…

18. Chúng tôi đã sẵn sàng quan sát. Hãy để cập nhật simulator và thử

Đây là đêm chung kết lớn. Năm, không, sáu mẫu đã kết hợp với nhau để tạo ra Duck Simulator tuyệt vời này. Không cần phải nói gì thêm, chúng tôi sẽ cho bạn thấy DuckSimulator!

Không có câu hỏi ngớ ngẩn

Hỏi: Vì vậy đây là một mẫu kết hợp?

Đáp: Không, đây chỉ là một tập hợp các mẫu làm việc cùng nhau. Một mẫu kết hợp là một tập hợp của một vài mẫu được kết hợp để giải quyết một vấn đề chung. Chúng tôi chỉ cần nhìn vào mẫu kết hợp Model-View-Controller; nó là một tập hợp của một vài mẫu đã được sử dụng nhiều lần trong nhiều giải pháp thiết kế.

Hỏi: Vì vậy, lợi ích thực sự của các Mẫu thiết kế là khi tôi gặp một vấn đề và bắt đầu áp dụng các mẫu cho đến khi tôi có giải pháp. Đúng không?

Đáp: Đó là một sai lầm. Chúng ta đã xem qua bài tập này với Ducks để cho bạn thấy các mẫu có thể phối hợp với nhau như thế nào. Bạn không bao giờ thực sự muốn tiếp cận một thiết kế như chúng ta vừa làm. Trong thực tế, có thể có các giải pháp cho các phần của trình mô phỏng vịt mà một số trong số các mẫu này là “quá mức cần thiết”. Đôi khi chỉ cần sử dụng các nguyên tắc thiết kế OO tốt cũng có thể tự giải quyết vấn đề đủ tốt. Chúng ta sẽ nói nhiều hơn về điều này trong chương tiếp theo, nhưng bạn chỉ muốn áp dụng các mẫu khi chúng có ý nghĩa. Bạn không bao giờ muốn bắt đầu với ý định sử dụng các mẫu chỉ vì lợi ích của nó. Bạn nên xem xét thiết kế của DuckSimulator là “cứng nhắc” và “nhân tạo”. Nhưng này, nó rất vui và cho chúng tôi một ý tưởng tốt về cách một số mẫu có thể phù hợp với một giải pháp.

Chúng ta đã làm những gì?

Chúng tôi bắt đầu với một loạt Quackables…

Một con ngỗng xuất hiện và muốn hành động như một Quackable. Vì vậy, chúng tôi đã sử dụng Mẫu Adapter để điều chỉnh con ngỗng thành Quackable. Bây giờ, bạn có thể gọi quack() trên một con ngỗng được bọc trong bộ chuyển đổi và nó sẽ kêu hork!

Sau đó, các nhà nghiên cứu quyết định họ muốn đếm các tiếng kêu quack. Vì vậy, chúng tôi đã sử dụng Mẫu Decorator để thêm một công cụ trang trí QuackCounter theo dõi số lần quack() được gọi, và sau đó ủy thác quack cho gói Quackable được bọc bên trong nó.

Nhưng các nhà nghiên cứu đã lo lắng rằng họ đã quên thêm công cụ trang trí QuackCounter. Vì vậy, chúng tôi đã sử dụng Abstract Factory Pattern để đảm nhiệm việc tạo ra những con vịt. Bây giờ, bất cứ khi nào họ muốn một con vịt, họ yêu cầu Factory tạo một con, và nó return lại một con vịt “chắc chắn” được trang trí (decorate). (Và đừng quên, họ cũng có thể sử dụng một nhà máy vịt khác (tạo một factory khác) nếu họ muốn một con vịt không cần trang trí!)

Chúng tôi có vấn đề về quản lý theo dõi tất cả những con vịt và ngỗng và quackables. Vì vậy, chúng tôi đã sử dụng Composite Pattern để nhóm các quackable thành Flock. Mẫu cũng cho phép nhà nghiên cứu tạo ra các Flock phụ để quản lý các “nhóm” vịt. Chúng tôi đã sử dụng Mẫu Iterator trong triển khai của mình bằng cách sử dụng java.util vào trình lặp iterator trong ArrayList.

Các nhà nghiên cứu cũng muốn được thông báo khi bất kỳ quackable nào kêu quack. Vì vậy, chúng tôi đã sử dụng Observer Pattern để cho phép nhà nghiên cứu (Quackologists) đăng ký làm Quackable Observers. Bây giờ, họ đã nhận được thông báo mỗi khi có bất kỳ tiếng kêu quacks nào. Chúng tôi đã sử dụng iterator một lần nữa trong việc cài đặt này. Các nhà nghiên cứu thậm chí có thể sử dụng Observer Pattern với composite của họ (Flock).

Một cái nhìn bằng mắt vịt chim: sơ đồ lớp

Sơ đồ lớp kết hợp các mẫu thiết kế 1

Sơ đồ lớp kết hợp các mẫu thiết kế 2

Đón xem phần 2: MVC – Vua của các mẫu kết hợp.

Đây là link đính kèm bản gốc của quyển sách: Head First Design Patterns.
Đây là link đính kèm sourcecode của sách: Tải SourceCode.