"Add responsibilities to objects dynamical"
The Decorator attaches additional responsibilities to an object
dynamically. The ornaments that are added to pine or fir trees are
examples of Decorators. Lights, garland, candy canes, glass ornaments,
etc., can be added to a tree to give it a festive look. The ornaments
do not change the tree itself which is recognizable as a Christmas tree
regardless of particular ornaments used. As an example of additional
functionality, the addition of lights allows one to "light up" a
Christmas tree.
Another example: assault gun is a deadly weapon on it's own. But you
can apply certain "decorations" to make it more accurate, silent and
devastating.
Rules of thumb
- Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
- Adapter changes an object's interface, Decorator enhances an object's responsibilities. Decorator is thus more transparent to the client. As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters.
- A Decorator can be viewed as a degenerate Composite with only one component. However, a Decorator adds additional responsibilities - it isn't intended for object aggregation.
- Decorator is designed to let you add responsibilities to objects without subclassing. Composite's focus is not on embellishment but on representation. These intents are distinct but complementary. Consequently, Composite and Decorator are often used in concert.
- Decorator and Proxy have different purposes but similar structures. Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests.
- Decorator lets you change the skin of an object. Strategy lets you change the guts.
Download full source code use abstract class - CSharp
Download full source code use interface - CSharp
Giữ vững nguyên tắc viết mã “Open-Close” hay “Luôn mở cho việc mở rộng, nhưng đóng cho việc sửa đổi”
Bạn đang làm nhân viên tư vấn Thiết Kế Mẫu tại công ty GigantoComputer, với mức lương khá cao và bạn đang ở trong căn tin công ty.
“Hôm nay có món gì?” bạn hỏi tay đầu bếp khó chịu đang đứng sau bếp nướng.
“Cho một cái hamburger,” bạn nói và xoay xoay cái khay trong tay.
Người đầu bếp mang cái hamburger đến bàn tính tiền, không quên hỏi lại “Có thêm thịt rán không?”
“Chắc chắn rồi”, bạn nói.
Người đầu bếp xóa phiếu ăn cũ trên máy tính tiền và làm lại phiếu ăn.
“Hamburger và thịt rán”. Vừa nói anh ta vừa gõ vào máy tính tiền.
“Cho thêm một ít pho mát” Bạn nói.
Người đầu bếp ném một ánh nhìn khó chịu , xóa cái phiếu ăn, mổ mổ cái bàn phím và nói “Hamburger với pho mát và thịt nướng. Ok. Đủ rồi chứ?”
“Hmm”, bạn nói, nhìn quét qua cái thực đơn “Hay là thêm một chút thịt xông khói?”
Người đầu bếp nhìn chằm chằm vào bạn và dường như định văng ra một vài câu khó chịu gì đó nhưng vẫn nhập phiếu ăn vào máy.
“Hey”, bạn nói. “Anh chắc chắn là được lợi nhiều hơn từ việc sử dụng mẫu thiết kế trang trí Decorator chứ hả?”
“Vâng”, anh đầu bếp trả lời, tự hỏi về những gì bạn nói “Tôi đã nói điều này cả ngàn lần rồi”
Bạn cầm cái Hamburger pho mát thịt xông khói với vẻ hạnh phúc và nói “Thêm một vài lát cà chua nữa thì tuyệt!”
Chương này nói về hai mẫu thiết kế quan trọng, nó sẽ lắp đầy những thiếu sót trong việc lập trình hướng đối tượng cơ bản, đặc biệt là ở khả năng kế thừa. Đây là hai mẫu trang trí Decorator và mẫu nhà máy Factory.
Mẫu trang trí Decorator là lựa chọn hoàn hảo cho tình huống tôi vừa nêu ở trên bởi vì ta đang nói về khả năng mở rộng chức năng cho một lớp có sẵn. Sau khi viết một lớp, bạn có thể thêm phần trang trí Decorator (các lớp mở rộng) để mở rộng lớp này. Khi đó bạn không phải sửa đổi lên lớp gốc. Kết quả là cái Hamburger của bạn trở thành Hamburger pho mát, rồi Hamburger pho mát thịt xông khói, mọi thứ thật dễ dàng.
Nguyên lý Open-Close – “Luôn Open cho việc mở rộng và Closed cho việc sửa đổi”
“Hôm nay có món gì?” bạn hỏi tay đầu bếp khó chịu đang đứng sau bếp nướng.
“Cho một cái hamburger,” bạn nói và xoay xoay cái khay trong tay.
Người đầu bếp mang cái hamburger đến bàn tính tiền, không quên hỏi lại “Có thêm thịt rán không?”
“Chắc chắn rồi”, bạn nói.
Người đầu bếp xóa phiếu ăn cũ trên máy tính tiền và làm lại phiếu ăn.
“Hamburger và thịt rán”. Vừa nói anh ta vừa gõ vào máy tính tiền.
“Cho thêm một ít pho mát” Bạn nói.
Người đầu bếp ném một ánh nhìn khó chịu , xóa cái phiếu ăn, mổ mổ cái bàn phím và nói “Hamburger với pho mát và thịt nướng. Ok. Đủ rồi chứ?”
“Hmm”, bạn nói, nhìn quét qua cái thực đơn “Hay là thêm một chút thịt xông khói?”
Người đầu bếp nhìn chằm chằm vào bạn và dường như định văng ra một vài câu khó chịu gì đó nhưng vẫn nhập phiếu ăn vào máy.
“Hey”, bạn nói. “Anh chắc chắn là được lợi nhiều hơn từ việc sử dụng mẫu thiết kế trang trí Decorator chứ hả?”
“Vâng”, anh đầu bếp trả lời, tự hỏi về những gì bạn nói “Tôi đã nói điều này cả ngàn lần rồi”
Bạn cầm cái Hamburger pho mát thịt xông khói với vẻ hạnh phúc và nói “Thêm một vài lát cà chua nữa thì tuyệt!”
Chương này nói về hai mẫu thiết kế quan trọng, nó sẽ lắp đầy những thiếu sót trong việc lập trình hướng đối tượng cơ bản, đặc biệt là ở khả năng kế thừa. Đây là hai mẫu trang trí Decorator và mẫu nhà máy Factory.
Mẫu trang trí Decorator là lựa chọn hoàn hảo cho tình huống tôi vừa nêu ở trên bởi vì ta đang nói về khả năng mở rộng chức năng cho một lớp có sẵn. Sau khi viết một lớp, bạn có thể thêm phần trang trí Decorator (các lớp mở rộng) để mở rộng lớp này. Khi đó bạn không phải sửa đổi lên lớp gốc. Kết quả là cái Hamburger của bạn trở thành Hamburger pho mát, rồi Hamburger pho mát thịt xông khói, mọi thứ thật dễ dàng.
Nguyên lý Open-Close – “Luôn Open cho việc mở rộng và Closed cho việc sửa đổi”
Một trong những khía cạnh quan trọng nhất trong quá trình phát triển
một ứng dụng là các nhà phát triển và lập trình viên phải đối đầu với sự
thay đổi, và đó là lý do vì sao các mẫu thiết kế này lại được giới
thiệu trước tiên. Có thể nói các Mẫu Thiết Kế sẽ giúp bạn giải quyết
được các sự thay đổi, và bạn có thể dễ dàng chuyển đổi mã nguồn của mình
cho các trường hợp mới và bất khả kháng. Như tôi đã nói qua trong suốt
cuốn sách này,lập trình viên thường tiêu tốn thời gian cho việc mở rộng
và thay đổi mã nguồn hơn là phát triển mã nguồn gốc.
Mẫu chiến lược Strategy đã được giới thiệu trước đây trong chương II,
giúp bạn xử lý những sự thay đổi bằng cách cho phép bạn chọn lựa một
thuật toán thích hợp từ một tập hợp thuật toán bên ngoài hơn là phải
viết lại mã nguồn. Mẫu trang trí Decorator cũng tương tự vậy, nó cho
phép bạn viết tiếp mã nguồn, tránh việc sửa đổi lên mã nguồn gốc, trong
khi vẫn đáp ứng được yêu cầu thay đổi. Đó là điểm chính yếu tôi muốn
nhấn mạnh.
Ghi nhớ: Hãy làm cho mã nguồn của bạn đáp ứng được nguyên tắc
“Luôn đóng cho sự chỉnh sửa, và luôn mở cho việc mở rộng” càng nhiều
càng tốt. Nói cách khác, hãy thiết kế mã nguồn sao cho không cần phải
thay đổi gì nhiều nhưng luôn có thể mở rộng khi cần.
Đây là một ví dụ cho việc viết mã nguồn luôn đóng cho sự thay đổi.
Công ty mà bạn đang làm tư vấn, công ty GigantoComputer, quyết định làm một cái máy vi tính mới. Đây là mã nguồn của lớp Computer:
public class cComputer
{
public cComputer() { }
public string description()
{
return "You're getting a computer";
}
}
Khi một đối tượng computer được khởi tạo. Phương thức description sẽ trả về văn bản “You’re getting a computer.”
. Tới giờ mọi việc vẫn tốt đẹp. Nhưng một số khách hàng quyết định rằng
họ muốn có một cái đĩa cứng trong máy tính. “Không vấn đề gì cả” Các
lập viên trong công ty đáp. “ Chúng ta chỉ cần chỉnh sửa mã nguồn lại
một chút như sau:”
public class cComputer
{
public cComputer() { }
public string description()
{
return "You're getting a computer and a disk";
}
}
Bây giờ khi một đối tượng computer được tạo và bạn gọi phương thức description, bạn sẽ nhận được văn bản “You’re getting a computer and a disk.” Nhưng một vài khách hàng vẫn chưa hài lòng. Họ muốn thêm một cái màn hình nữa. Và thế là các lập trình viên phải chỉnh sửa tiếp như sau:
public class cComputer
{
public cComputer() { }
public string description()
{
return "You're getting a computer and a disk and a monitor";
}
}
Bây giờ, khi bạn tạo một computer và gọi phương thức description bạn sẽ thấy
"You're getting a computer and a disk and a monitor";
Bạn có thể thấy vấn đề ở đây: Các lập trình viên phải thay đổi mã
nguồn mỗi khi khách hàng thay đổi yêu cầu của họ. Rõ ràng, đó là vấn đề
chính.
Và bạn, với cương vị là tư vấn mẫu thiết kế, sẽ chỉnh sửa nó.
Tôi phải nhắc lại một lần nữa: càng nhiều càng tốt, hãy viết mã nguồn
của bạn đóng cho việc sửa đổi, nhưng mở cho việc mở rộng. Trong chương
II, bạn đã biết cách làm việc với mẫu chiến lược Strategy. Đó là, bạn
đóng gói mã nguồn vào các thuật toán riêng biệt để sử dụng dễ dàng, hơn
là việc xử lý chúng thông qua các lớp con.
Mẫu trang trí Decorator có một cách tiếp cận khác. Thay vì sử dụng
một thuật toán bên ngoài, mẫu thiết kế này sử dụng một phương pháp “bao
bọc” mã nguồn của bạn để mở rộng chúng.
Ghi nhớ: Định nghĩa chính thức của mẫu trang trí Decorator trong sách của GoF có viết: “Gắn kết thêm một số tính năng cho đối tượng
một cách linh động. Mẫu trang trí Decorator cung cấp một phương pháp
linh hoạt hơn là sử dụng lớp con để mở rộng chức năng cho đối tượng”
Mẫu thiết kế này được gọi là “Người trang trí” Decorator nhưng dường
như đó là tên gọi rườm rà. Một cái tên tốt hơn cho mẫu này có thể là
“Người tăng thêm” Augmentor hay “Người mở rộng” Extender bởi vì nó cho
phép bạn: tăng thêm hay mở rộng một lớp một cách linh động khi chương
trình được thực thi. Tuy nhiên, như bạn thấy trong chương này, thuật ngữ
“Người trang trí” Decorator còn giúp bạn hiểu rõ hơn khái niệm “đóng
cho việc chỉnh sửa, mở cho việc mở rộng”. Khi bạn làm hành động bao bọc
mã nguồn để mở rộng thêm chức năng, bạn không cần thiết chỉnh sửa lại mã
nguồn cũ, bạn chủ yếu tập trung vào việc trang trí nó.
Và đây là cách mà nó làm việc. Bạn bắt đầu với một cái máy tính computer đơn giản sau:
Khi bạn gọi phương thức description, bạn nhận được kết quả “You’re getting a computer”.
Bây giờ bạn muốn thêm ít phần cứng, một ổ cứng mới chẳng hạn. Trong
trường hợp này, bạn có thể thêm một lớp bao bọc wrapper như sau:
Bây giờ khi bạn gọi phương thức description của lớp bao bọc wrapper, nó sẽ gọi phương thức description của đối tượng computer để nhận được kết quả “You’re getting a computer “ và đối tượng ổ cứng disk sẽ trả về kết quả “and a disk”. Kết quả bạn nhận được “You’re getting a computer and a disk”
Nếu bạn muốn thêm vài thứ nữa vào lớp máy tính Computer, bạn hãy đặt nó vào lớp bao bọc wrapper, ví dụ như thêm vào cái màn hình Monitor:
Bây giờ khi bạn gọi phương thức description, mọi việc sẽ xảy ra như sau:
– Đối tượng computer, sẽ thực hiện phương thức description để tạo ra kết quả “You’re getting a computer”
– Đối tượng disk, sẽ thực hiện tiếp phương thức trên để thêm vào “and a disk”
– Đối tượng monitor, tiếp tục thực hiện phương thức description để thêm vào “and a monitor”
– Kết quả là bạn nhận được “You’re getting a computer and a disk and a monitor”
VÍ DỤ VỀ MẪU TRANG TRÍ DECORATOR
Example:Bạn bắt đầu viết một lớp máy tính Computer đơn giản, với một phương thức description trả về kết quả “computer” như sau:
public class cComputer
{
public cComputer() { }
public string description()
{
return "computer";
}
}
OK. Bạn đã hoàn thành cái máy tính đơn giản. Bây giờ làm sao để tạo một
lớp trang trí? Những lớp này hoạt động như là một lớp bao bọc cho lớp Computer, điều này có nghĩa là phải có một biến để lưu trữ một đối tượng computer. Một cách đơn giản để tạo lớp bao bọc wrapper là mở rộng lớp Computer.
Tạo dựng một Lớp trang trí Decorator
Bài này tôi dùng 2 cách, cách 1: là dùng abstract và cách 2 dùng interface trong CSharp, tôi có để lại source mẫu, bạn có thể xem và tham khảo :) Trong khi trình bày tôi dùng interface vì nó sẽ giúp các bạn dễ hiểu hơn
Download full source code use abstract class - CSharp
Download full source code use interface - CSharp
Download full source code use abstract class - CSharp
Download full source code use interface - CSharp
public interface IComputer
{
string description();
}
Thêm vào một đĩa cứng Disk
Đây là lớp bao bọc Disk , sẽ thêm một ổ cứng vào máy tính. Bởi vì đây là một lớp bao bọc, nó cần phải biết đang bao bọc thứ gì. Vì vậy bạn đưa cho nó một đối tượng computer ngay khi nó khởi tạo. Lớp bao bọc Disk sẽ lưu trữ một đối tượng tên computerBây giờ bạn cần hiện thực phương thức Description. Phương thức mới này sẽ gọi phương thức description của lớp computer và thêm vào dòng chữ “and a disk” như sau:
public class cDisk : IComputer
{
IComputer _icomp;
public cDisk(IComputer c)
{
_icomp = c;
}
public string description()
{
return _icomp.description() + " and a disk";
}
}
Vậy là bạn đã bao bọc đối tượng computer, và khi bạn gọi phương thức description của đối tượng disk này, nó sẽ gọi phương thức description của lớp computer, đồng thời thêm vào dòng chữ “and a disk”. Kết quả bạn sẽ có “computer and a disk”
Thêm vào một màn hình monitor
Tương tự với monitorpublic class cMonitor : IComputer
{
IComputer _icomp;
public cMonitor(IComputer c)
{
_icomp = c;
}
public string description()
{
return _icomp.description() + " and a Monitor";
}
}
OK. Bạn đã có đầy đủ các lớp. Giờ là lúc chạy thử nghiệm chương trình.
Đầu tiên bạn tạo đối tượng computer như sau:
static void Main(string[] args)
{
IComputer comp = new cComputer(); // implements interface IComputer
Console.ReadLine();
}
Sau đó bạn bao bọc đối tượng computer để thêm vào một Monitor
static void Main(string[] args)
{
IComputer comp = new cComputer(); // implements interface IComputer
// add 1 Monitor
comp = new cMonitor(comp);
Console.ReadLine();
}
Bây giờ hãy thêm vào 2 Disk, Cuối cùng bạn gọi phương thức description của lớp bao bọc để xem kết quả:
static void Main(string[] args)
{
IComputer comp = new cComputer(); // implements interface IComputer
// add 1 Monitor
comp = new cMonitor(comp);
// and 2 Disk
comp = new cDisk(comp);
comp = new cDisk(comp);
Console.WriteLine(string.Format("you're getting: {0}" , comp.description()));
Console.ReadLine();
}
OK. Khi chạy chương trình bạn nhận được kết quả.
you're getting: a computer and a monitor and a disk and a disk
Không tồi. Bạn đã mở rộng một đối tượng gốc thật đơn giản bằng cách bao bọc nó trong nhiều lớp trang trí decorator khác nhau, tránh việc phải chỉnh sửa trong mã nguồn gốc. Và đó là Mẫu Thiết Kế Trang Trí Decorator.
Download full source code use abstract class - CSharp
Download full source code use interface - CSharp
Ref:
https://haihth.wordpress.com/2013/02/21/dp-chapter3/
https://sourcemaking.com/design_patterns/decorator