close up photo of programming of codes

codecungnhau.com

Một trang web về kỹ thuật lập trình

Design Patterns: Decorator (Wrapper)

Mục tiêu

Decorator là một mẫu thiết kế cấu trúc cho phép bạn đính kèm các hành vi mới vào các đối tượng bằng cách đặt các đối tượng này bên trong các đối tượng bao bọc đặc biệt có chứa các hành vi mới.

Vấn đề

Hãy tưởng tượng rằng bạn đang làm việc với một thư viện thông báo cho phép các chương trình khác thông báo cho người dùng của họ về các sự kiện quan trọng.

Phiên bản ban đầu của thư viện dựa trên lớp Notifier chỉ có một số trường, một hàm tạo và một phương thức gửi duy nhất. Phương thức có thể chấp nhận một đối số thông báo từ một client và gửi thông báo đến một danh sách các email đã được truyền cho notifier thông qua hàm tạo của nó. Ứng dụng của bên thứ ba hoạt động như một client phải tạo và định cấu hình đối tượng notifier một lần, sau đó sử dụng nó mỗi khi có điều gì đó quan trọng xảy ra.

Một chương trình có thể sử dụng lớp notifier để gửi thông báo về các sự kiện quan trọng tới một nhóm email được xác định trước.

Tại một thời điểm, bạn nhận ra rằng người dùng thư viện mong đợi nhiều hơn là chỉ thông báo qua email. Nhiều người trong số họ muốn nhận được tin nhắn SMS về các vấn đề quan trọng. Những người khác muốn được thông báo trên Facebook và tất nhiên, người dùng doanh nghiệp sẽ thích nhận được thông báo từ Slack.

Mỗi loại thông báo được triển khai dưới dạng lớp con của Notifier.

Điều này khó đến mức nào? Bạn đã mở rộng lớp Notifier và đưa các phương thức thông báo bổ sung vào các lớp con mới. Bây giờ client phải khởi tạo lớp thông báo mong muốn và sử dụng nó cho tất cả các thông báo tiếp theo.

Nhưng sau đó một người nào đó đã hỏi bạn lý do, “Tại sao bạn không thể sử dụng nhiều loại thông báo cùng một lúc? Nếu ngôi nhà của bạn bị cháy, bạn muốn được thông báo qua mọi kênh”.

Bạn đã cố gắng giải quyết vấn đề đó bằng cách tạo các lớp con đặc biệt kết hợp nhiều phương thức thông báo trong một lớp. Tuy nhiên, rõ ràng rằng cách tiếp cận này sẽ làm phình to mã, không chỉ mã thư viện mà cả mã client. Bạn phải tìm một số cách khác để cấu trúc các lớp thông báo sao cho số lượng của chúng không vô tình phá vỡ một số kỷ lục Guinness.

Sự bùng nổ tổ hợp của các lớp con.

Giải pháp

Mở rộng một lớp là điều đầu tiên nghĩ đến khi bạn cần thay đổi hành vi của một đối tượng. Tuy nhiên, việc thừa kế này có một số lưu ý nghiêm trọng mà bạn cần lưu ý.

  • Tính kế thừa là tĩnh. Bạn không thể thay đổi hành vi của một đối tượng hiện có trong thời gian chạy. Bạn chỉ có thể thay thế toàn bộ đối tượng bằng một đối tượng khác được tạo từ một lớp con khác.
  • Các lớp con có thể chỉ có một lớp cha. Trong hầu hết các ngôn ngữ, kế thừa không cho phép một lớp kế thừa các hành vi của nhiều lớp cùng một lúc.

Một trong những cách để khắc phục những hạn chế này là sử dụng Kết hợp (Aggregation) hoặc Hợp thành (Composition) thay vì Kế thừa. Cả hai lựa chọn này hoạt động gần như giống nhau: một đối tượng có một tham chiếu đến một đối tượng khác và ủy quyền cho nó một số hoạt động, trong khi với kế thừa, bản thân đối tượng có thể thực hiện công việc đó, kế thừa hành vi từ lớp cha của nó.

Với cách tiếp cận mới này, bạn có thể dễ dàng thay thế đối tượng “helper” được liên kết bằng một đối tượng khác, thay đổi hành vi trong thời gian chạy. Một đối tượng có thể sử dụng hành vi của nhiều lớp khác nhau, có tham chiếu đến nhiều đối tượng và ủy quyền cho chúng tất cả các loại công việc. Kết hợp hoặc Hợp thành là nguyên tắc quan trọng đằng sau nhiều mẫu thiết kế, bao gồm cả Decorator.

Kế thừa và Kết hợp

“Wrapper” là biệt hiệu thay thế cho mẫu Decorator thể hiện rõ ràng ý tưởng chính của mẫu. Wrapper là một đối tượng có thể được liên kết với một số đối tượng đích. Wrapper chứa cùng một tập hợp các phương thức như đích và ủy quyền cho nó tất cả các yêu cầu mà nó nhận được. Tuy nhiên, wrapper có thể thay đổi kết quả bằng cách thực hiện điều gì đó trước hoặc sau khi nó chuyển yêu cầu tới đích.

Khi nào một Wrapper đơn giản trở thành Decorator thực sự? Như đã đề cập, wrapper triển khai giao diện giống như đối tượng được bao bọc. Đó là lý do tại sao từ góc nhìn của client, các đối tượng này giống hệt nhau. Làm cho trường tham chiếu của wrapper chấp nhận bất kỳ đối tượng nào tuân theo giao diện. Điều này sẽ cho phép bạn bao một đối tượng trong nhiều wrapper, thêm hành vi kết hợp của tất cả các wrapper vào nó.

Trong ví dụ về thông báo ở trên, hãy để hành vi thông báo email đơn giản bên trong lớp Notifier cơ sở, nhưng chuyển tất cả các phương thức thông báo khác thành Decorator.

Các phương thức thông báo khác nhau trở thành Decorator.

Clien sẽ cần bao một đối tượng notifier cơ bản thành một tập hợp các decorator phù hợp với ý định của client. Các đối tượng kết quả sẽ được cấu trúc như một ngăn xếp.

Ứng dụng có thể cấu hình các ngăn xếp phức tạp của decorator.

Decorator cuối cùng trong ngăn xếp sẽ là đối tượng mà client thực sự làm việc với. Vì tất cả các client đều triển khai giao diện giống như notifier cơ sở, phần còn lại của client sẽ không quan tâm liệu nó hoạt động với đối tượng notifier “thuần túy” hay đối tượng decorator.

Chúng ta có thể áp dụng phương pháp tương tự cho các hành vi khác như định dạng thông báo hoặc soạn danh sách người nhận. Client có thể trang trí đối tượng bằng bất kỳ Decorator tùy chỉnh nào, miễn là chúng tuân theo cùng một giao diện với những cái khác.

Cấu trúc

  1. Component khai báo giao diện chung cho cả đối tượng bao bọc và được bao bọc.
  2. Concrete Component là một lớp các đối tượng được bao bọc. Nó xác định hành vi cơ bản, có thể được thay đổi bởi decorator.
  3. Lớp Base Decorator có một trường để tham chiếu đến một đối tượng được bao bọc. Kiểu của trường phải được khai báo là giao diện Component để nó có thể chứa cả Concrete Component và Decorator. Decorator cơ sở ủy quyền tất cả các hoạt động cho đối tượng được bao bọc.
  4. Concrete Decorator xác định các hành vi bổ sung có thể được thêm vào các component một cách động. Concrete Decorator ghi đè các phương thức của Decorator cơ sở và thực thi hành vi của chúng trước hoặc sau khi gọi phương thức cơ sở.
  5. Client có thể bọc các component trong nhiều decorator, miễn là nó hoạt động với tất cả các đối tượng thông qua giao diện Component.

Khả năng áp dụng

Sử dụng mẫu Decorator khi bạn cần gán các hành vi bổ sung cho các đối tượng trong thời gian chạy mà không phá vỡ mã sử dụng các đối tượng này.

Sử dụng mẫu khi cảm thấy khó xử hoặc không thể mở rộng hành vi của đối tượng bằng cách sử dụng tính năng kế thừa.

Ưu và nhược điểm

😄😄😄

Có thể mở rộng hành vi của một đối tượng mà không cần tạo một lớp con mới.

Có thể thêm hoặc xóa trách nhiệm khỏi một đối tượng trong thời gian chạy.

Có thể kết hợp một số hành vi bằng cách gói một đối tượng thành nhiều decorator.

Nguyên tắc Đơn Trách nhiệm. Có thể chia một lớp nguyên thực thi nhiều biến thể có thể có của hành vi thành một số lớp nhỏ hơn.

🙁🙁🙁

Khó xóa một wrapper cụ thể khỏi ngăn xếp decorator.

Khó triển khai decorator theo cách mà hành vi của nó không phụ thuộc vào thứ tự trong ngăn xếp decorator.

Mã cấu hình ban đầu của các lớp có thể trông khá xấu.

Mối quan hệ với các mẫu khác

  • Adapter thay đổi giao diện của một đối tượng hiện có, trong khi Decorator mở rộng một đối tượng mà không thay đổi giao diện của nó. Ngoài ra, Decorator hỗ trợ đệ quy, điều này không thể thực hiện được khi bạn sử dụng Adapter.
  • Adapter cung cấp một giao diện khác cho đối tượng được bao bọc, Proxy cung cấp cho nó cùng một giao diện và Decorator cung cấp cho nó một giao diện nâng cao.
  • Chain of Responsibility (CoR) và Decorator có cấu trúc lớp rất giống nhau. Cả hai mẫu đều dựa vào thành phần đệ quy để truyền việc thực thi qua một loạt đối tượng. Tuy nhiên, có một số khác biệt quan trọng.
    Các trình xử lý CoR có thể thực hiện các hoạt động tùy ý độc lập với nhau. Chúng cũng có thể ngừng truyền yêu cầu vào bất kỳ lúc nào. Ngược lại, các Decorator khác nhau có thể mở rộng hành vi của đối tượng trong khi vẫn giữ cho nó nhất quán với giao diện cơ sở. Ngoài ra, decorator không được phép phá vỡ dòng thực thi (flow) của yêu cầu.
  • Composite và Decorator có các sơ đồ cấu trúc tương tự vì cả hai đều dựa vào thành phần đệ quy để tổ chức một số lượng đối tượng có kết thúc mở.
    Decorator giống như Composite nhưng chỉ có một thành phần con. Có một sự khác biệt đáng kể khác: Decorator thêm các trách nhiệm bổ sung cho đối tượng được bao bọc, trong khi Composite chỉ “tổng hợp” các kết quả con của nó.
    Tuy nhiên, các mẫu cũng có thể hợp tác : có thể sử dụng Decorator để mở rộng hành vi của một đối tượng cụ thể trong cây Composite.
  • Các thiết kế sử dụng nhiều Composite và Decorator thường có thể được hưởng lợi từ việc sử dụng Prototype. Áp dụng mẫu cho phép sao chép các cấu trúc phức tạp thay vì xây dựng lại chúng từ đầu.
  • Decorator cho phép thay đổi giao diện của một đối tượng, trong khi Strategy cho phép thay đổi cách thức thực thi của đối tượng.
  • Decorator và Proxy có cấu trúc tương tự, nhưng nội dung rất khác nhau. Cả hai mẫu đều được xây dựng trên nguyên tắc hợp thành (composition), trong đó một đối tượng được cho là ủy quyền một số công việc cho đối tượng khác. Điểm khác biệt là Proxy thường tự quản lý vòng đời của đối tượng của nó, trong khi các thành phần của Decorator luôn được kiểm soát bởi client.

Chương trình tham khảo

C++

C#

Python


Đã đăng vào

trong

bởi

Bình luận

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *