The Open-Closed Principle
Posted On Wednesday, August 13, 2008 at at 4:50 PM by Unknown- Có rất nhiều kinh nghiệm quý được rút ra trong lĩnh vực thiết kế hướng đối tượng. Ví dụ như “tất cả biến member nên được khai báo private”, hay “không nên sử dụng biến toàn cục”, hay “sử dụng chức năng kiểm tra kiểu lúc runtime (run time type identification - RTTI) rất nguy hiểm và nên hạn chế”. Vậy các kinh nghiệm quý đó bắt đầu từ đâu? Ai đã kiểm chứng tính đúng đắn của những kinh nghiệm này và liệu chúng luôn luôn đúng? Bài viết này sẽ đề cập đến một nguyên tắc cơ bản, nguyên tắc nền tảng cho các kinh nghiệm quý trên. Nguyên tắc Open-Closed (Open – Closed principle).
- Ivar Jacobson nói rằng: “Tất cả những hệ thống phần mềm thay đổi suốt quá trình hoạt động của chúng. Phải tâm niệm rằng phần mềm luôn được khách hàng mong đợi sẽ hoạt động hoàn hảo hơn so với phiên bản đầu tiên của nó“
-Làm sao để có thể thiết kế được một sản phẩm có tính ổn định cao trong khi vẫn cho phép sửa đổi suốt quá trình hoạt động? Bertrand Meyer đưa ra một nguyên tắc rất nổi tiếng vào năm 1988 :
NÊN ĐƯỢC OPEN ĐỂ MỞ RỘNG, NHƯNG CLOSED ĐỐI VỚI NHỮNG SỬA ĐỔI.
- Khi có một sửa đổi nào đó vào chương trình mà dẫn đến nhiều thay đổi ở những nơi khác, chương trình đó được xem là một thiết kế tồi (bad design) và nó trở nên khó sửa đổi cũng như khó sử dụng lại. Nguyên tắc open-closed sẽ giúp khắc phục điểm yếu này của phần mềm theo một cách rất đơn giản. Nó nói rằng bạn nên thiết kế những modules mà không bao giờ phải sửa đổi. Khi yêu cầu (requirement) có thay đổi, bạn mở rộng những behavior của những module này bằng cách thêm code mới thay vì sửa những dòng code cũ đang chạy tốt.
Mô tả nguyên tắc open-closed:
- Những modules được xem tuân theo nguyên tắc open-closed khi nó có hai tính chất sau:
1. Chúng “Open For Extension”.
+ Điều này có nghĩa là những behavior của module có thể được mở rộng. Chúng ta có thể làm cho module chạy theo một cách khác khi yêu cầu của ứng dụng thay đổi.
2. Chúng “Closed for Modifictation”.
+ Source code của module không được sửa đổi trong mọi trường hợp.
- Dường như hai điều trên mâu thuẫn với nhau. Cách thông thường để mở rộng một behavior của module là sửa lại code của module đó. Vậy làm cách nào để giải quyết mâu thuẫn này?
Sử dụng Abstraction – Sự trừu tượng hóa.
- Trong một số bài viết trước, đã có lần chúng ta nói đến abstraction, trong các bài viết/dịch của tôi, các Abstraction sẽ được tạm hiểu như các Interface hoặc Abstract class trong C#.
- Khi sử dụng các nguyên tắc của ngôn ngữ lập trình hướng đối tượng (3 NGUYÊN TẮC CƠ BẢN CHẮC AI CŨNG BIẾT) như C++, C#, chúng ta có thể thể tạo ra những abstractions để tượng trưng cho một nhóm vô tận những behavior có thể được thay đổi về sau. Abstractions ở đây có thể là các abstract base classes trong C++ hay C#, có thể là một Interface như trong C# và Java, và một nhóm các behavior có thể thay đổi được đề cập ở trên chính là các lớp con. Một module của bạn chỉ cần biết đến abstraction này và sử dụng nó. Module loại này sẽ đạt được tính “closed for modification” bởi vì nó chỉ phụ thuộc chặt chẽ vào một abstraction nào đó đã cố định. Còn những behavior của module có thể được mở rộng bằng cách thêm các lớp mới, mở rộng từ abstraction của bạn.
- Hình 1 dưới đây là ví dụ về một thiết kế không theo nguyên tắc open-closed. Cả lớp Client và lớp Server đều là các lớp cụ thể. Không có gì đảm bảo rằng các member functions của lớp Server là virtual. Bởi vì lớp Client với lớp Server có quan hệ “uses”, nên khi chúng ta muốn một đối tượng thuộc kiểu Client sử dụng một đối tượng thuộc kiểu Server, chúng ta phải sửa tên lớp khai báo trong lớp Client thành tên mới của lớp server mong muốn.
Hình 1: Closed Client
Hình 2: Open Client
Vài ví dụ
The Shape Abstraction
- Các bạn hãy xem một ví dụ kinh điển sau. Ta cần làm một chương trình có thể vẽ các hình hình học. Lúc đầu người ta yêu cầu chỉ cần vẽ hình tròn và hình vuông và các hình này phải được vẽ theo một trình tự nhất định. Một danh sách các hình tròn và hình vuông sẽ được tạo ra theo một thứ tự nào đó và chương trình phải duyệt qua danh sách đó để vẽ ra hết các hình trong danh sách.
Trong thủơ mới học lập trình, chúng ta thường được dạy lập trình kiểu thủ tục và tất nhiên cách tiếp cận ấy hoàn toàn không tuân theo nguyên tắc open-closed. Chúng ta sẽ giải quyết vấn đề này trong đoạn code dưới đây. Ở đây chúng ta thấy một tập các cấu trúc dữ liệu được khai báo để mô tả một đối tượng hình học. Thành phần thứ nhất của struct dùng để xác định một đối tượng là hình tròn hay hình vuông. Function DrawAllShapes sẽ duyệt qua danh sách các hình và xác định xem từng hình là hình gì sau đó gọi function vẽ tương ứng (hoặc DrawCircle hoặc DrawSquare).
enum SharpType
{
Circle,
Square
};
class Sharp
{
SharpType Type;
}
class Circle : Sharp
{
double Radius;
Point Center;
}
class Square : Sharp
{
double Side;
Point TopLeft;
}
//
// These functions are implemented elsewhere
//
void DrawSquare(Square obj)
{
}
void DrawCircle(Circle obj)
{
}
void DrawAllShapes(Sharp[] list, int n)
{
int i;
for (i = 0; i < n; i++)
{
Sharp s = list[i];
switch (s.Type)
{
case SharpType.Square:
DrawSquare((Square)s);
break;
case SharpType.Circle:
DrawCircle((Circle)s);
break;
}
}
}
Code 1
- Function DrawAllShapes trên không tuân theo nguyên tắc open-closed bởi vì rõ ràng nó không thể “closed” khi muốn thêm khả năng vẽ hình tam giác, hình chữ nhật, hình cô gái.
- Tất nhiên chương trình này chỉ là một ví dụ nhỏ. Trong thực tế, câu lệnh switch trong function DrawAllShapes có thể phải lặp lại nhiều lần trong những function khác. Nếu muốn thêm một hình mới vào chương trình kiểu này bạn phải tìm mọi chỗ có đoạn switch hoặc if/else như trên để sửa.
- Đoạn code dưới đây sẽ giải quyết vấn đề ta đang gặp phải với open-closed. Trong trường hợp này, ta sẽ dùng 1 lớp abstract class Shape. Bên trong lớp này sẽ khai báo một hàm virtual tên là Draw. Các lớp hình học cụ thể kế thừa từ lớp này sẽ implement lại hàm virtual Draw cho chính nó.
class Sharp
{
public virtual void Draw()
{
// Implement default behavior, or do nothing here
}
}
class Circle : Sharp
{
public override void Draw()
{
// Implement function draw a Circle
}
}
class Square : Sharp
{
public override void Draw()
{
// Implement function draw a Square
}
}
void DrawAllShapes(Sharp[] list)
{
foreach(Sharp s in list)
s.Draw();
}
Code 2
- Rất dễ thấy rằng nếu muốn mở rộng behavior của function DrawAllShapes trong hình trên, ta chỉ cần thêm một lớp mới kế thừa từ lớp abstract Shape. Function DrawAllShapes không cần thiết phải sửa lại, vì thế DrawAllShapes đã thỏa mãn yêu cầu của nguyên tắc open-closed.
- Trong thực tế, lớp abstract Shape có thể có thêm những method khác. Còn việc thêm một lớp con của lớp Shape vào chương trình thì khá đơn giản vì ta chỉ cần implement tất cả các method abstract/virtual cần thiết. Và hiển nhiên làm theo cách này ta sẽ không cần phải tìm trong code cũ của chương trình những chỗ có switch, if/else để sửa đổi.
Các phương án để đạt được tính "closed" của chương trình:
- Thực sự thì không có một chương trình nào có thể 100% “closed”. Có một ví dụ như sau, bạn hãy nghĩ xem sẽ phải làm như thế nào nếu ta muốn function DrawAllShapes phải vẽ những hình tròn trước các hình vuông. Rõ ràng hàm DrawAllShapes hiện tại không thể “closed” với những yêu cầu như thế này. Nói chung, cho dù module của bạn có “closed” như thế nào thì vẫn có những thay đổi nhỏ mình phải chấp nhận.
- Bởi vì tính đóng không hoàn toàn trọn vẹn 100%, chúng ta cần có chiến thuật hợp lý cho nó. Có nghĩa là một người thiết kế hướng đối tượng nên xem xét những gì có thể sẽ thay đổi và những gì nên “closed” trong thiết kế của anh ta và điều này đòi hỏi nhiều kinh nghiệm trong thiết kế phần mềm. Một designer giàu kinh nghiệm biết tường tận về người sử dụng hoặc thị trường phần mềm của mình, từ đó có thể xác định những thay đổi có thể có trong chương trình. Và nhờ đó anh ta có thể chắc chắn rằng nguyên tắc open-closed sẽ được dùng trong hầu hết những nơi có thay đổi trong tương lai.
Using Abstraction to Gain Explicit Closure
- Vậy làm thế nào để “close” function DrawAllShapes trong bài toán vẽ theo thứ tự? Xin nhắc lại là tính đóng của chương trình dựa trên abstraction. Vì thế, để làm cho function DrawAllShapes “closed” đối với thứ tự vẽ, có lẽ ta nên nghĩ đến làm thế nào để trừu tượng hóa cái thứ tự vẽ đó. Trường hợp vẽ theo thứ tự nói trên thực ra là thao tác vẽ một số loại hình trước các loại hình khác.
- Nếu có một cách để xác định thứ tự của các đối tượng cùng một kiểu. Nếu cho hai đối tượng nào đó, ta phải tìm ra đối tượng nào nên được vẽ trước. Vì thế, chúng ta có thể viết một method của lớp Shape tên là Precedes chẳng hạn để so sánh chính nó với một đối tượng thuộc kiểu Shape khác và trả về giá trị bool. Giá trị bool trả về là true thì đối tượng ban đầu nên được vẽ trước đối tượng được dùng để so sánh.
- Trong các ngôn ngữ hướng đối tượng như C++, C#, chúng ta có thể implement ý tưởng của function Precedes trên bằng cách sử dụng overloaded operator <. Hình 3 sẽ hiện thực phần code này. Như vậy, chúng ta đã có cách để xác định thứ tự trước sau của hai đối tượng thuộc kiểu Shape, chúng ta có thể sort chúng và sau đó chỉ việc vẽ ra theo thứ tự mong muốn. - Nhưng chúng ta vẫn chưa hoành chỉnh abstraction behavior so sánh của đối tượng Shape. Với khai báo abstract như trong lớp Shape, mỗi đối tượng thuộc kiểu Shape phải override lại method Precede. Nhưng họ đã làm điều đó như thế nào?
public class Sharp
{
public virtual void Draw()
{
}
public virtual bool Precedes(Sharp otherSharp)
{
return true;
}
public static bool operator <(Sharp sharp1, Sharp sharp2)
{
return sharp1.Precedes(sharp2);
}
}
Code 3
public class Circle : Sharp
{
public override bool Precedes(Sharp otherSharp)
{
try
{
Square temp = (Square)otherSharp;
}
catch
{
return false;
}
return true;
}
}
Code 4
- Rõ ràng với việc ép kiểu như vậy chúng ta đã vi phạm nguyên tắc open-closed. Và dường như không có cách nào hàm so sánh này có thể “closed” khi muốn kiểm tra Circle với một hình học loại khác. Và mỗi khi một lớp con của lớp Shape được tạo ra, hàm compare này của chúng ta buộc lòng phải được sửa lại không chỉ ở lớp Circle mà ở toàn bộ những lớp con đã có của lớp Shape.
Using a “Data Driven” Approach to Achieve Closure
- Tính “closed” ở các lớp con của lớp Shape có thể đạt được bằng cách sử dụng một cheat code như sau. Ta sẽ tạo ra một bảng các tên của lớp theo thứ tự mong muốn, từng lớp con của lớp Shape sẽ dựa vào các giá trị trong bảng này và tên của chính nó để đưa ra kết quả so sánh. Hãy xem hình 6 để hiểu rõ thêm cách thực hiện.
- Theo cách tiếp cận này chúng ta đã thành công trong việc “closed” function DrawAllShapes trước những vấn đề nảy sinh, và tính “closed” của mỗi lớp con Shapes khi có một lớp mới thêm vào. Thậm chí khi có yêu cầu thay đổi quy luật vẽ của các hình thì các lớp con Shapes cũng không cần sửa đổi gì cả.
- Tuy nhiên vẫn có một thành phần vẫn chưa “closed” với những loại khác nhau của Shapes, đó chính là bản thân bảng danh sách tên đang sử dụng. Nhưng ta vẫn có thể đưa phần code định nghĩa bảng này sang một module độc lập với những module còn lại trong chương trình, vì thế bất cứ sự thay đổi nào cũng không ảnh hưởng đến những module khác.
- Có nói mãi cũng không hết chuyện về các vấn đề trong ví dụ Shapes. Tuy nhiên nếu hàm vẽ các hình theo một thứ tự khác không xét instance Shapes thuộc loại gì thì lại nảy sinh nhiều vấn đề khác nữa. Có vẽ như chúng ta muốn sắp xếp thứ tự vẽ của các hình theo nhiều kiểu khác nhau và ta sẽ không bàn tiếp nữa trong bài viết này. Và như các bạn thấy, không thể 100% “closed” trong thiết kế phần mềm được.
Heuristic and Conventions
- Như đã đề cập ở phần đầu của bài viết, nguyên tắc open-closed là nền tảng chính cho rất nhiều các kinh nghiệm và quy ước khác được công bố liên quan đến OOD trong nhiều năm. Dưới đây là một vài điều quan trọng nhất trong số đó.
Make all Member Variables Private.
- Đây là điều quan trọng nhất trong các conventions của thiết kế hướng đối tượng. Các member variable bên trong class nên được biết đến bởi các methods của lớp định nghĩa chúng. Các member variables không nên được truy xuất bởi lớp khác, bao gồm cả các lớp con. Vì thế chúng nên được khai báo private thay vì public hoặc protected.
- Khi một member variables của một lớp thay đổi, mỗi function có sử dụng biến này sẽ phải thay đổi theo. Vì thế không có một function nào của lớp có thể “closed” khi cứ phụ thuộc vào member variable kiểu này.
- Trong thiết kế hướng đối tượng, chúng ta mong muốn các method của class không nhất thiết phải “closed” đối với những thay đổi của member variables bên trong class. Tuy nhiên chúng ta muốn bất cứ những lớp khác, bao gồm cả lớp con phải “closed” đối với những thay đổi của các variables này. Chúng ta có tên cho điều này, đó chính là “Encapsulation”.
- Bây giờ, nếu ta có một member variable mà ta biết sẽ không bao giờ cần thay đổi? Vậy liệu có lí do nào để khai báo nó private? Ví dụ, hình 5 thể hiện một lớp Device có một member variable kiểu bool tên là status. Biến variable này giữ trạng thái của lần hoạt động cuối cùng của object kiểu Device. Nếu lần chạy cuối thành công thì biến status này giữ giá trị true và ngược lại.
class Device
{
public bool Status;
}
Code 5
- Chúng ta biết rằng kiểu(bool) và ý nghĩa (meaning) của variable này sẽ không bao giờ đổi. Vậy tại sao lại không khai báo nó là public và để chương trình (client code) có thể đọc giá trị của nó? Nếu biến variable này thật sự không thay đổi, và nếu tất cả những phần trong chương trình chỉ đọc giá trị status thì khi đó biến public này sẽ không gây tác hại gì cả. Tuy nhiên, có thể ở một nơi nào, bởi một người khác trong nhóm của bạn, có thay đổi giá trị status và điều này có thể dẫn đến chương trình chạy sai mà bạn không ngờ tới. Vì vậy có thể không đáng để chúng ta mạo hiểm khai báo như trên.
No Global Variables -- Ever
- Vấn đề tranh cãi về biến toàn cục cũng tương tự với biến member public. Không một module nào có sử dụng một biến toàn cục lại có thể “closed” đối với những module tương tự nhưng lại có khả năng ghi vào biến toàn cục này. Bất kì module nào có sử dụng biến dùng chung nhưng thay đổi nó tùy tiện có thể ảnh hưởng đến những nơi khác trong chương trình.
- Tuy nhiên, nếu có những biến toàn cục được sử dụng rất ít bởi một số nơi, hoặc chắc chắn rằng nó sẽ được sử dụng một cách nhất quán, thì nó sẽ không gây tác hại gì với chương trình. Designer phải xem xét xem mức độ “closed” của ứng dụng so với lợi ích có được khi sử dụng biến toàn cục trong một số trường hợp.
- Như vậy, tương tự như public member variables, vấn đề sử dụng biến toàn cục sẽ tùy vào phong cách, nhu cầu thiết kế của designer. Trong một số trường hợp, sử dụng biến toàn cục sẽ rất tiện lợi và có thể tăng performance cho ứng dụng. Trong các trường hợp như vậy, không nên máy móc theo open-closed mà bỏ đi biến toàn cục trong khi sử dụng nó cũng chẳng gây ra lỗi gì.
RTTI is Dangerous
- Một nguyên tắc khác cũng rất phỏ biến đó là sử dụng dynamic_cast. Người ta thường khuyên nên tránh sử dụng các chức năng ép kiểu trong các ngôn ngữ lập trình, rằng nó nguy hiểm và nên tránh sử dụng càng nhiều càng tốt. Hãy xem ví dụ 6 đã vi phạm nguyên tắc open-closed khi sử dụng dinamic_cast như thế nào:
class Sharp
{
}
class Circle : Sharp
{
public void DrawCircle()
{
}
}
class Square : Sharp
{
public void DrawSquare()
{
}
}
void DrawAllShapes(Sharp[] list)
{
foreach(Sharp s in list)
{
Square square;
Circle circle;
try { square = (Square)s; } catch {}
try { circle = (Circle)s; } catch {}
if (square != null)
square.DrawSquare();
else if (circle != null)
circle.DrawCircle();
}
}
Code 6
- Tuy nhiên hình 7 cho ta thấy trong một trường hợp khác, vẫn sử dụng dynamic_cast nhưng không vi phạm nguyên tắc open-closed. Đoạn code chỉ là ví dụ minh họa vì có nhiều cách khác tốt hơn để implement method DrawOnlyCircles.
class Sharp
{
public virtual void Draw()
{
}
}
class Circle : Sharp
{
}
void DrawOnlyCircles(Sharp[] list)
{
foreach(Sharp s in list)
{
Circle circle;
try { circle = (Circle)s; } catch {}
if (circle != null)
circle.Draw();
}
}
Code 7
- Điểm khác nhau giữa hai cách trên là trong hình 6, code bắt buộc phải được sửa lại khi có một lớp con mới của lớp Shape được thêm vào. Trong khi đó, hình 7 cho ta thấy rằng vẫn có sử dụng dynamic_cast nhưng vẫn không vi phạm open-closed và lớp mới được thêm vào sẽ không làm đoạn code này chạy sai. Nói chung, nếu RTTI không ảnh hưởng nguyên tắc open-closed thì nó an toàn để sử dụng.
Kết luận
- Có nhiều điều để bàn tiếp về nguyên tắc open-closed nhưng nhiều người đồng ý rằng nguyên tắc này là nền tảng của thiết kế hướng đối tượng. Tuân theo nguyên tắc này sẽ giúp ta đạt được những lợi ích của hướng đối tượng: đó là khả năng sử dụng lại và dễ bảo trì. Tuy nhiên, để đạt được điều đó không đơn giản là viết phần mềm = ngôn ngữ lập trình hướng đối tượng. Mà hơn thế, nó đòi hỏi người thiết kế phải apply các abstraction và những phần của chương trình mà anh ta cho rằng nó có thể được thay đổi/mở rộng trong tương lai.
Dịch và bịa lại từ: The Open-Closed Principle
Tham khảo: http://objectmentor.com/resources/publishedArticles.html
MVP - Model View Presenter
Posted On Monday, June 30, 2008 at at 6:05 PM by UnknownGiới thiệu
- Trong bài viết giới thiệu về mô hình MVC, chúng ta đã đề cập đến các nhược điểm của ngôn ngữ ASP. Có thể thấy hiện giờ ít trang web nào sử dụng công nghệ ASP cũ để phát triển nữa mà thay vào đó là các ngôn ngữ mới hơn, tiện ích hơn như PHP, ASP.NET, Java, v.v. Microsoft giới thiệu ngôn ngữ ASP.NET với điểm nhấn tách riêng các thành phần giao diện và xử lý dữ liệu bussiness logic bằng mô hình code-behind. ASP.NET có vẽ như là một lựa chọn tốt cho các ứng dụng web đơn giản, nhưng chính mô hình code-behind sẽ gây ra một số khó khăn cho các ứng dụng web vừa và lớn:
- Code-behind dường như được thiết kế để làm quá nhiều việc, nó vừa là một nơi để xử lý các event, kiểm soát các workflow trong chương trình và nó còn là một layer trung gian giữa dữ liệu bên dưới và giao diện phía trên. Người lập trình có thể viết mọi thứ trong code-behind và làm cho nó trở nên vô cùng rối rắm. Để cho code-behind làm quá nhiều thứ sẽ làm cho code của bạn khó kiểm soát và khó test (Unit Test). Trong những ứng dụng web lớn, người ta xem một design tốt khi nó giảm thiểu sự phụ thuộc lẫn nhau giữa các tầng (layer) và làm sao để cho code-behind càng đơn giản càng tốt. Với mô hình Model-View-Presenter, chúng ta sẽ thấy rằng code-behind được giữ cho vô cùng đơn giản và không hề dính dáng gì đến những xử lý trên giao diện Web.
- Một nhược điểm khác của code-behind là nó hầu như rất khó để sử dụng lại những xử lý giao diện (presentation logic) giữa những trang code-behind khác nhau nếu không đưa các xử lý đó ra các lớp Utility hay Helpers để tránh trùng lặp code. Tất nhiên để làm được điều đó cần một thời gian để tổ chức các lớp cho thich hợp. Tuy nhiên, nó thường dẫn chúng ta đến việc thiết kế các lớp một cách rời rạc và giống như khi sử dụng ASP. Trong một design tốt, mỗi class trong hệ thống nên có duy nhất một nhiệm vụ và mục đích rõ ràng, nếu chúng ta cần tạo ra một class chỉ để tránh trùng lặp code giữa 2 hoặc nhiều nơi khác thì đó là một dấu hiệu xấu, một thiết kế tồi.
- Và một nhược điểm cố hữu mà tôi thường nhắc đến là khả năng sử dụng Unit Test để test các lớp code-behind này. Các lớp code-behind đều kế thừa từ System.Web.UI.Page nên khó có thể viết các Unit Test một cách tự nhiên và thuận lợi được.
- Đã có nhiều kĩ thuật được tìm ra để giải quyết vấn đề của code-behind. Chẳng hạn như Castle Monorail project đã kế thừa những lợi điểm của Ruby-On-Rails và không sử dụng mô hình ASP.NET event. Thực ra cũng giống như ASP.NET MVC Framework, Castle Monorail hướng người lập trình đến cách phát triển ứng dụng Web theo MVC hơn là MVP, và khi áp dụng những framework này, chúng ta sẽ làm web ASP.NET theo một cách hoàn toàn khác, không có postback, không có server controls,... Khác với các framework hỗ trợ MVC, các framework hỗ trợ MVP là những giải pháp trong đó vẫn giữ mô hình event của ASP.NET nhưng hướng đến mục tiêu làm cho code-behind càng đơn giản càng tốt. Model-View Presenter sẽ giúp bạn làm được chuyện đó mà không cần dựa trên một framework nào cả. Đó cũng chính là lý do rất nhiều lập trình viên Web đã chọn MVP thay vì MVC trước khi có những framework hỗ trợ MVC như hiện nay.
- Bên cạnh các ASP.NET MVC Framework, Castle Monorail, hiện nay đã có những framework hỗ trợ MVP cho .NET như MVC# Framework và NMVP Framework. Hi vọng tôi có thời gian tìm hiểu và chia sẽ với các bạn muốn quan tâm. Trong bài viết này, tôi sẽ trình bay khái quát lý thuyết suông về MVP; một sample code sẽ được giới thiệu trong bài tiếp theo.
- Nhiều người cho rằng MVP là một design biến đổi của MVC. Điểm khác nhau dễ thấy nhất là Presenter và Controller
- Trong môi trường .NET, cùng một Presenter có thể được sử dụng cho View trên web như các trang ASP.NET hoặc được sử dụng cho các Form trong Windows Form Application. Các Presenter đọc và ghi dữ liệu thông qua một Interface của .NET nên nó hoàn toàn độc lập với layer View. Chính nhờ cách làm này mà ta có thể áp dụng Unit Test cho các lớp xử lý Presenter rất dễ dàng và nó cũng chính là một trong những lợi ích lớn nhất của MVP cho khả năng tái sử dụng.
Cập nhật View
- Khi thành phần dữ liệu Model được cập nhật, View cũng sẽ phải được cập nhật để hiển thị những thay đổi. Quá trình cập nhật View có thể được thực hiện bằng nhiều cách khác nhau. Có lẽ cũng từ những cách này mà Model-View-Presenter được chia thành hai loại là Passive View và Supervising Controller.
- Trong mô hình Passive View, Presenter sẽ cập nhật view tương ứng bằng cách đọc dữ liệu trực tiếp từ Model rồi set lên View, do đó các lớp View sẽ không biết gì về thay đổi dữ liệu bên dưới. Hay nói cách khác chúng hoàn toàn bị động theo như trên gọi.
- Còn trong mô hình Supervising Controller, các lớp View sẽ tương tác trực tiếp với Model bên dưới để đọc dữ liệu mà không cần thông qua các lớp Presenter. Presenter sẽ cập nhật Model; nó chỉ thay đổi những control trên View trong trường hợp có những xử lý giao diện phức tạp mà không thể khai báo trước chẳng hạn như ẩn hiện control, đổi màu text,…
- Quyết định dùng Passive View hay Supervising Controller thường dựa trên nhu cầu test tự động của ứng dụng. Nếu bạn muốn làm một ứng dụng có thể test tự động dể dàng và cover hầu hết các xử lý bên trong thì Passive View là một lựa chọn thích hợp, mọi UI logic đều được test bằng cách viết lớp test cho các Presenter. Mặt khác, nếu bạn thích code đơn giản hơn thì Supervising Controller có vẻ là một lựa chọn thích hợp hơn. Đối với những thay đổi đơn giản trên giao diện, bạn cũng không cần đưa những xử lý cho các thay đổi đó vào lớp Presenter. Khi chọn lựa giữa Passive View và Supervising Controller, bạn nên cân nhắc các điều sau đây:
- Cả hai cách đều cho phép bạn tăng khả năng test tự động cho các xử lý giao diện (presentation logic)
- Passive View thường cung cấp khả năng test cao hơn Supervising Controller bởi vì tất cả các xử lý giao diện được đặt trong các lớp Presenter.
- Supervising Controller sẽ đỡ phải code nhiều như Passive View bởi vì Passive View không cần thực hiện những cập nhật đơn giản cho View.
Tương tác với dữ liệu
- Bạn cần phân biệt rõ Model với Domain, hai khái niệm này hoàn toàn khác nhau. Chúng ta có thể implement các lớp tương tác với dữ liệu bằng nhiều cách khác nhau. Trong một số trường hợp, có thể bạn sẽ phải sử dụng Observer Pattern cho các Presenter và Model. Nó sẽ giúp cho Presenter nhận những events từ Model khi có sự thay đổi và từ đó cập nhật thay đổi đó lên trên View. Thông thường, người ta sẽ làm ra các lớp Service/Manager, các lớp này sẽ đóng vai trò như các lớp Bussiness Logic trong layer Model để giúp Presenter tương tác với database bên dưới:
Các nhược điểm của MVP
- Có nhiều lớp, nhiều layer phải implement; có nghĩa độ phức tạp của ứng dụng sẽ tăng.
- Bạn phải nghĩ ra một cách để kết hợp giữa các Views và các Presenters. Trong ASP.NET thì các lớp Code-Behind sẽ implement một interface IView nào đó và sử dụng một member Presenter.
- Mô hình Model không biết gì về Presenter. Vì thế nếu dữ liệu Model bị thay đổi từ một chương trình khác thay vì bởi Presenter thì phải nghĩ ra một cách để Presenter biết. Các bạn có thể implement chức năng này nếu cần bằng cách sử dụng các events, hoặc tham khảo thiết kế Observer Pattern.
Tài liệu tham khảo:
http://msdn.microsoft.com/en-us/library/cc304728.aspx
http://www.codeproject.com/KB/architecture/ModelViewPresenter.aspx
http://en.wikipedia.org/wiki/Model_View_Presenter
Trong bài sau tôi sẽ trình bày một sample cho Supervisinng Controller.
MVC Model View Controller
Posted On Sunday, June 22, 2008 at at 3:22 PM by Unknown- Mấy tuần nay tôi đang tìm hiều về các pattern thiết kế giao diện và đã gặp nhiều vướng mắc. Một số đã tự giải quyết được, một số vẫn còn tồn đọng. Vì vậy tôi cũng muốn chia sẽ những gì mình đã tìm hiểu với mọi người. Tôi quyết định dịch và viết bài từ mô hình ra đời lâu nhất và được nhiều người biết đến nhất, đó là mô hình MVC. Ban đầu tôi đã định không tìm hiểu về MVC và đi vào những thiết kế mới nhất như MVP nhưng thực ra MVC vẫn còn rất phổ biến hiện nay. (Cám ơn Tính nai oan oan cho các thông tin về MVC và Monorail) Vì vậy, trong những loạt bài sau tôi sẽ viết nhiều về pattern đang phổ biến hiện nay như MVP và các ví dụ thực tế của nó. Phần lớn sẽ là bài dịch và sửa đổi cho dễ hiểu, hiện nay vẫn chưa đủ trình độ để sáng tạo cái gì cả.
- Có thể nói rằng các chương trình dù là Win Application hay Web Application đều gồm hai thành phần chính là giao diện và dữ liệu. Các ứng dụng chúng ta tạo ra sẽ đọc dữ liệu từ nơi lưu trữ và hiển thị lên cho người dùng thấy. Sau khi người sử dụng chương trình thao tác với ứng dụng và thay đổi giá trị dữ liệu, chương trình sẽ lưu các thay đổi đó xuống nơi lưu trữ. Do luồng thông tin chính của các ứng dụng sẽ truyền từ nơi lưu trữ lên trên phần hiển thị và ngược lại nên người có thể cho rằng nên có cách nào đó thiết kế hai thành phần dữ liệu và giao diện cùng với nhau để giảm thiểu việc coding và tăng performance. Dù không cố tình làm như vậy nhưng đa số những bạn mới viết chương trình cơ sở dữ liệu lần đầu tiên thường không biết nên sẽ làm theo cách này. Cách làm như vậy có một số vấn đề nghiêm trọng. Một trong các vấn đề đó là việc giao diện thường có xu hướng thay đổi nhiều hơn so với dữ liệu lưu trữ. Hơn nữa, những xử lý tính toán (business logic) trong chương trình cũng là một thành phần thường xuyên thay đổi trong suốt quá trình phát triển phần mềm. Nếu chương trình được xây dựng theo kiểu trộn lẫn code của các thành phần data access, business logic và presentation thì sẽ rất khó bảo trì sửa đổi cũng như khả năng sử dụng lại. Chính vì mục đích tăng tính độc lập giữa các thành phần trong ứng dụng và để giải quyết những trở ngại trên, người ta đã nghĩ ra nhiều mô hình và cách thiết kế ứng dụng rất hiệu quả. Kể từ những năm 70, mô hình MVC được nghĩ ra để giải quyết một số vấn đề trên.
Đặt vấn đề:
- Khi lần đầu tiên viết chương trình đọc dữ liệu từ database và hiển thị lên form, tôi đã viết chương trình kiểu gom chung phần xử lý giao diện và truy xuất dữ liệu làm một. Có nghĩa là tôi tạo ra câu query sql ngay trong code của lớp giao diện. Cách làm này rất không hợp lý vì khi sửa đổi cấu trúc bảng trong cơ sở dữ liệu thì câu query sql sẽ cần phải đổi, dẫn đến phải sửa lớp giao diện. Khi cần đổi database khác thì gần như phải viết lại từ đầu. Như vậy gom chung giữa giao diện và dữ liệu là không nên, và ai cũng biết điều đó.
- Liệu có một cách nào chúng ta có thể chia nhỏ chương trình thành nhiều module để tiện thay đổi, bảo trì?
Giả sử người ta có các yêu cầu sau:
- Giao diện của ứng dụng có xu hướng thay đổi nhiều hơn là những thay đổi business logic của chương trình, đặt biệt đối với những ứng dụng Web. Ví dụ như cùng một trang Web hiển thị sản phẩm có thể sẽ được yêu cầu thay đổi nhiều lần về cách bố trí sản phẩm, màu sắc bố cục trong khi cách để lấy thông tin sản phẩm từ database sẽ không cần sửa đổi. Hơn nữa, một trong những tiện lợi của ứng dụng Web là người ta có thể thay đổi giao diện vào bất cứ lúc nào mà không cần người dùng phải install cài đặt gì cả. Nếu như những đoạn code xử lý giao diện và xử lý business logic bên trong chương trình được viết chung với nhau, bạn sẽ phải thay đổi những lớp dùng để xử lý business logic mỗi khi cần thay đổi giao diện. Mà khi xử lý business logic của ứng dụng bị thay đổi thì có thể sẽ gây ra các lỗi không mong muốn nên phải test mọi thứ lại từ đầu cho dù đó chỉ là một thay đổi nhỏ về giao diện. Như vậy ta có một nhu cầu về việc tách riêng giữa giao diện và các xử lý business logic.
- Trong một số ứng dụng, chương trình sẽ hiển thị cùng một dữ liệu bằng nhiều cách khác nhau. Ví dụ như chúng ta cần làm một loạt các biểu đồ khác nhau hiển thị giá chứng khoán, các biểu đồ này được hiển thị cùng lúc. Nếu người dùng thay đổi dữ liệu trên một màn hình nào đó thì hệ thống phải cập nhật thay đổi đó trên các màn hình còn lại. Ta có một nhu cầu về tự cập nhật giao diện khi có những thay đổi trong dữ liệu.
- Để thiết kế giao diện HTML đẹp và hiệu quả cần những kĩ thuật đặt biệt, có khi cả năng khiếu về mĩ thuật mà không phải một lập trình viên nào cũng có được. Vì vậy trong những dự án lớn thường có những người design giao diện riêng và công việc của họ chỉ là thiết kế layout, còn lập trình viên sẽ thực hiện các xử lý business logic của ứng dụng. Do đó nhu cầu cho việc tách rời phần design giao diện và phần lập trình được đặt ra để tăng hiệu quả cho quá trình làm phần mềm.
- Có một yêu cầu khác được đặt ra như sau: những thao tác trên giao diện thông thường sẽ gồm có hai phần: hiển thị và cập nhật. Thành phần hiển thị sẽ lấy dữ liệu từ nơi lưu trữ, định dạng lại nếu cần thiết và hiển thị lên màn hình. Khi người sử dụng thực hiện một số thao tác, những gì thay đổi sẽ được thành phần cập nhật gọi thành phần xử lý business logic để cập nhật các thay đổi đó xuống nơi lưu trữ. Như vậy thành phần hiển thị sẽ tự nó thực hiện việc lấy thông tin, trong khi những thao tác của người dùng sẽ được xử lý bởi một thành phần khác.
- Trong các ứng dụng Web, khi bạn click vào một link trên 1 trang, một thao tác ứng với link đó sẽ được thực hiện đồng thời một trang mới sẽ được hiển thị. Trong nhiều trường hợp, trang web cần hiển thị sau cùng sẽ không liên quan trực tiếp đến thao tác được gọi khi click vào link ở trang trước. Ví dụ: trong những trang Web mua bán trực tuyến, khi muốn xem danh sách những món hàng mình muốn mua trước khi thanh toán, bạn sẽ click vào phần giỏ hàng. Một danh sách những món hàng sẽ hiện ra kèm với các link hoặc button xóa chúng khỏi giỏ hàng. Nếu bạn đổi ý và muốn bỏ đi một món nào đó thì bạn sẽ click vào link hoặc button tương ứng. Sau đó, giỏ hàng sẽ lại hiện ra với danh sách những món hàng mới mà không có món bạn vừa xóa. Vì vậy, ứng dụng phải hiển thị lại cùng 1 trang với cùng một danh sách nào đó sau khi thực hiện hai thao tác khác hẳn nhau trong cùng một HTTP request. Vậy có một yêu cầu là nhiều xử lý phức tạp sẽ được thực hiện trong cùng một request.
- Ngày nay, giao diện của một ứng dụng nên được độc lập với thiết bị. Nếu bạn muốn ứng dụng Web của mình có thể được hiển thị nhanh và đẹp trên các thiết bị di dộng thì bạn phải sửa đổi nhiều ở phần giao diện và chắc chắn là những business logic sẽ không cần thay đổi gì cả. Một sự tách biệt rõ ràng giữa hai thành phần này sẽ giúp bạn đạt được yêu cầu đó vài giảm thiểu những rủi ro khi phải thay đổi business logic do sai lầm khi thiết kế lúc đầu. Vậy dẫn đến yêu cầu về thiết kế ứng dụng để dễ dàng hỗ trợ nhiều dạng thiết bị khác nhau chứ không riêng gì máy tính.
- Và cuối cùng, để phần mềm của bạn có thể được test một cách toàn diện đôi khi không khả thi và rất mất thời gian. Hiện nay người ta có thể sử dụng Unit Test để test các lớp business logic nhanh chóng chính xác và tự động trong hầu hết trường hợp. Vì thế càng làm cho những xử lý tách rời khỏi phần giao diện sẽ giúp chương trình của bạn dễ viết code test hơn. Vậy yêu cầu thiết kế ứng dụng cho dễ test bằng code cũng được đặt ra.
MVC sẽ đạt được mọi yêu cầu trên:
- Mẫu thiết kế Model-View-Controlller (MVC) tách rời phần dữ liệu domain, module hiển thị và những xử lý thao tác của người dùng thành ba nhóm lớp (class) riêng biệt:
Model: là những gì liên quan đến dữ liệu của ứng dụng. Model sẽ chịu trách nhiệm thao tác trực tiếp với database như truy xuất, cập nhật, thêm mới. Model sẽ lấy thông tin và trả về cho View, nó cũng sẽ chịu trách nhiệm thông báo cho Controller những thay đổi dữ liệu nếu có. Bạn có thể xem Model bao gồm các lớp DAL (Data Access Layer) và các lớp Domain.
View: View sẽ chịu trách nhiệm hiển thị thông tin như tên của nó. Ta có thể xem View như các trang web, hoặc các form trong ứng dụng windows.
Controller: là những lớp sẽ lắng nghe những thao tác chuột và bàn phím từ người dùng, nó sẽ tác động đến Model và View để thay đổi khi cần thiết.
Hình dưới mô tả mối quan hệ giữa Model, View và Controller, Model sẽ không biết gì về View và
Controller.
Hình: MVC Class Structure
- Theo như hình trên thì ta thấy cả View và Controller đều phụ thuộc và có thể gọi đến Model. Tuy nhiên, Model lại hoàn toàn độc lập với View và Controller. Điều này là một trong những lợi ích chính của mô hình MVC khi tách rời các thành phần Model View và Controller. Sự độc lập của Model giúp chúng ta build và viết code test cho nó mà không ảnh hưởng hoặc phụ thuộc đến những lớp hiển thị. Ngoài ra, sự tách rời View và Controller ra hai nhóm lớp như trên là một thuận lợi thứ hai đối với những ứng dụng có nhiều người dùng. Ví dụ như trong một số cách làm, người ta có thể lập trình phần kiểm tra quyền (Role) ngay trong phần xử lý giao diện. Khi theo cách làm MVC, người ta sẽ phải tách riêng phần kiểm tra quyền ra khỏi phần hiển thị và nó hoàn toàn thuận lợi khi cần sửa đổi hoặc thêm những trang mới.
Những lợi ích về test khi áp dụng MVC
- Sự dễ dàng trong viết code test là một thuận lợi khi áp dụng MVC. Test những component của chương trình trở nên rất khó khăn khi chúng phụ thuộc chặt chẽ vào nhau, đặc biệt đối với những thành phần giao diện. Để test giao diện của một phần mềm, tất nhiên bạn phải qua các bước cài đặt và đôi khi điều đó trở nên rất mất thời gian khi chỉ để test một chức năng đơn giản. Tệ hơn khi xảy ra lỗi, chúng ta sẽ rất khó để phát hiện lỗi ở phần nào. Đó là lý do tại sao chia nhỏ các thành phần chức năng là một trong những chiều hướng chính của các thiết kế quan trọng. MVC chia nhỏ các vấn đề như lưu trữ, hiển thị và cập nhật dữ liệu thành 3 nhóm components, những component này có thể được test độc lập với nhau.
- Ngoài vấn đề về sự phụ thuộc, giao diện của phần mềm cũng rất phức tạp khi muốn test. Người ta thường sử dụng người thật để test giao diện, hoặc sẽ phải viết những script test để giả lập những thao tác của con người. Để viết những script này thường rất mất thời gian và phức tạp. MVC KHÔNG giải quyết được vấn đề về test giao diện, NHƯNG nó tách rời phần dữ liệu Model ra khỏi những xử lý hiển thị và cho phép MODEL có thể được test độc lập với phần hiển thị và điều đó sẽ giảm thiểu những test case liên quan đến giao diện.
Ý kiến của Nguyễn Thoại:
- Tôi đã không dịch và đưa vào những dạng biến đổi của mẫu MVC vào bài này vì không muốn các bạn cảm thấy rối thêm, cũng không đưa vào những lợi ích và giới hạn của MVC. Lợi ích thì tất nhiên đã được đề cập tới ở trên nên cũng không cần tổng kết lại làm gì, còn giới hạn chính là tính phức tạp. Cũng như những bài viết trước, các Pattern luôn phức tạp nhưng khi chịu khó một chút thì về sau sẽ được những lợi ích lớn.
- Các bạn có thể đặt câu hỏi: áp dụng MVC vô ASP.NET như thế nào. Có ví dụ code sample nào không. Xin thưa là có trang ví dụ ở đây, nhưng cá nhân tôi vẫn còn nghi ngờ tính đúng đắn của nó. Sắp tới, tôi sẽ viết thêm một số bài nữa liên quan đến việc áp dụng MVC trong.NET để tìm hiểu những vấn đề tôi còn nghi ngờ. Trong bài viết này không có ví dụ vì tôi sẽ dành phần code sample cho các bài sau. Hi vọng qua bài dịch này các bạn có được khái niệm MVC là cái gì, và lợi ích của nó như thế nào và khoan hãy nghĩ đến cách áp dụng. Cũng giống như những pattern khác (ở các bài viết sau), MVC được nghĩ ra với mục đích tách rời các thành phần quan trọng trong ứng dụng để tăng tính dễ bảo trì, dễ test tự động và khả năng sử dụng lại. Cùng một mục đích như vậy nhưng các mẫu pattern khác như MVP sẽ có cách thực hiện và cách cài đặt khác.
Liệu mô hình ASP.NET của Microsoft với các trang aspx và code behind tách rời có phải là một implement MVC? Ở bài sau tôi sẽ trả lời câu hỏi đó và giới thiệu khái quát về một framework MVC Framework cho ASP.NET.
Phỏng vấn 2 mẫu thiết kế Abstract Factory và Factory Pattern
Posted On Friday, June 13, 2008 at at 6:01 PM by UnknownNguyễn Thoại: Chà chà, chào hai anh, đây là lần đầu tiên blog của iem phỏng vấn hai anh, hai anh cảm thấy thế nào.
Factory Method: Tôi ko biết sao hay bị gộp chúng với cha Abstract Factory. Đều là factory pattern không có nghĩa chúng tôi không được có những buổi phỏng vấn riêng.
Nguyễn Thoại: Ấy ấy, đừng tự ái, em muốn phỏng vấn cả hai anh cùng nhau để giúp đọc giả phân biệt được ai là ai. Hai anh có nhiều điểm giống nhau, và em có nghe vài phàn nàn rằng người ta thỉnh thoảng bị rối và không thể phân biệt được 2 người.
Abstract Factory: Ừ đúng đấy, đã có lúc tôi bị nhầm lẫn là cha Factory Method, và tôi biết ổng cũng bị giống như tôi. Hai tụi tôi đều giỏi trong việc giảm sự phụ thuộc giữa chương trình với những cài đặt cụ thể; nhưng mỗi người chúng tôi lại có những cách làm riêng nên thỉnh thoảng dân lập trình lại cảm thấy rối và không thể xác định được phải dùng cách nào.
Factory Method: Ấy, người ta vẫn phân biệt được tôi mà. Nói chung, tôi dùng các lớp để tạo ra products, còn cha dùng những đối tượng; hai cách đó hoàn toàn khác nhau nhé.
Nguyễn Thoại: Ừ em có tìm hiểu về hai anh rồi mà vẫn còn rối đây này, anh có thể nói thêm về khoản này không, anh Factory Method?
Factory Method: Ok, Cả cha Abstract Factory và tôi đều tạo ra các products, các objects, đó là việc chúng tôi phải làm. Nhưng tôi làm nhờ vào sự kế thừa (inheritance) …
Abstract Factory: … còn tôi làm nhờ vào sự kết hợp các đối tượng.
Factory Method: Đúng thía, nghĩa là nếu muốn tạo ra các đối tượng bằng cách Factory Method, người ta cần phải extend một lớp và override lại hàm tạo “factory method”.
Nguyễn Thoại: …rồi factory method sẽ làm cái gì?
Factory Method: Tất nhiên là tạo ra 1 object roài, anh đã từng viết bài về tôi mà giờ còn hỏi hả. Ý tưởng của tôi, Factory Method Pattern, là sẽ sử dụng các lớp con để sinh ra 1 đối tượng mong muốn. Bằng cách đó, người dùng sẽ chỉ cần biết đến lớp trừu tượng như gia cầm, và các lớp con cụ thể sẽ lo về các kiểu gà, kiểu vịt, kiểu ngan. Vì vậy, nói theo cách khác, tôi giúp chương trình độc lập với các kiểu (type) cụ thể đó.
Abstract Factory: Ý tưởng của tôi cũng giống giống vậy đó, nhưng chỉ là làm theo một cách khác…
Nguyễn Thoại: Tiếp đi anh Abstract Factory … vừa rồi anh có đề cập đến cách kết hợp các đối tượng?
Abstract Factory: Ờ,cách của tôi thế này: Tôi tạo ra một kiểu trừu tượng (abstract type) để dùng vào việc tạo ra một nhóm những products khác. Khi đó, những lớp con của kiểu trừu tượng sẽ xác định cách tạo ra các products này. Để áp dụng được ý tưởng của tôi, bạn phải tạo ra một instance của một trong các lớp con trên (instance này là 1 factory) và đưa nó vào chỗ cần thiết trong code. Vì thế, giống như Factory Method, những nơi sử dụng factory của tôi sẽ hoàn toàn độc lập với những produtcts cụ thể.
Nguyễn Thoại: Oh yè, tôi biết rồi, một lợi ích nữa của cách này là anh đã nhóm các products tương tự nhau lại, có đúng không?
Abstract Factory: Đúng roài cưng à.
Nguyễn Thoại: Bây giờ người ta cần bổ sung thêm một product nữa vô nhóm các products mà anh có thể tạo ra. Vậy không cần phải thay đổi gì hết huh?
Abstract Factory: Hèm, trường hợp này thì tôi phải thay đổi chút ít, nhiều người không thích lắm ở điểm này.
Factory Method: hĩ hĩ
Abstract Factory: Cười gì cha nội?
Factory Method: Cười chứ sao ko cười, làm vậy quá mất công chứ gì nữa. Cha thay đổi có nghĩa là người dùng phải đi mà đổi tất cả các lớp con roài? (các lớp con ở đây là các factories)
Abstract Factory: Đúng, nhưng cần phải làm như vậy vì tôi có khả năng tạo ra nhiều kiểu product khác nhau, trong khi người ta xài cách của anh khi họ cần tạo ra một kiểu product nào đó thôi, vì thế anh không có cồng kềnh như tôi, anh chỉ cần một method duy nhất.
Nguyễn Thoại: Anh Abstract Factory bớt nóng, tôi nghe đồn rằng anh thường sử dụng nhiều hàm factory method theo cách của anh Factory Method để tạo các đổi tượng bên trong những factories của anh, điều đó có đúng ko?
Abstract Factory: Đúng đó em, anh thừa nhận điêu này. Những lớp factory con thường dùng các factory method để tạo các products tương ứng. Trong trường hợp này, các factory method được dùng thuần túy để tạo ra các products.
Factory Method: … còn tôi dùng hàm factory method để tạo ra product cụ thể mà người ta muốn, người dùng sẽ không biết cái gì được tạo ra, họ chỉ cần gọi hàm.
Nguyễn Thoại: Dường như hai anh làm rất tốt công việc của mình. Tôi chắc rằng đọc giả đã có được lựa chọn của mình. Sau cùng, những factories của 2 anh quả thật rất hữu ích, đọc giả của tôi sẽ sử dụng chúng trong các tình huống thích hợp nhất. Các anh đều đóng gói (encapsulate) quá trình tạo ra đối tượng để giúp chương trình độc lập và giảm phụ thuộc với những kiểu cụ thể, việc làm này thật đáng tuyên dương. Cám ơn hai anh, hai anh có lời gì trước khi chào tạm biệt đọc giả không ạ?
Abstract Factory: Cám ơn. Hãy nhớ đến tôi, Abstract Factory, và sử dụng tôi bất cứ khi nào bạn cần cùng một lúc tạo ra nhiều loại products, và khi bạn muốn chắc chắn những nơi sử dụng sẽ không cần biết đến những lớp cụ thể khi cần làm việc này.
Factory Method: Còn tôi là Factory Method, hãy sử dụng tôi để làm cho chương trình của bạn độc lập với những lớp cụ thể mà bạn cần tạo 1 đối tượng, hoặc khi bạn không biết sau này sẽ cần đến những lớp con nào nữa. Khi cần sử dụng tôi, hãy tạo tạo ra subclass (1 factory implement 1 kiểu abstract) và implement factory method cho chính bạn.
(Page 158-162 of Head First – Design Pattern)
Sử dụng Adapter Pattern Trong Lập Trình Hướng Đối Tượng
Posted On Friday, June 6, 2008 at at 4:57 PM by UnknownI/ Giới thiệu.
- Khái niệm Adapter đã xuất hiện từ rất lâu và được sử dụng rất nhiều trong cuộc sống. Thiết bị điện tử của bạn không thể sạc pin bằng điện 220V mà phải thông qua một bộ chuyển, biến điện 220 thành 12V. Bạn có một con chuột với Jack cắm USB nhưng máy đã hết chỗ cắm, người bán tặng bạn thêm 1 bộ chuyển từ USB sang PS2 để giải quyết vấn đề trên…Đó là những ví dụ hay gặp nhất về Adapter, và vì chúng quá thông dụng nên ít ai ngờ đến cái mình đang sử dụng hằng ngày lại là một kiểu adapter nào đó. Từ lâu, khái niệm Adapter đã được 4 đại ca về Design Pattern đưa vào lĩnh vực làm phần mềm và nó tỏ ra có ích không kém. Tôi dám chắc là các bạn ai đều cũng từng sử dụng đến Adapter trong cuộc sống của mình, thế thì tại sao lại không sử dụng nó trong lập trình để giải quyết những vấn đề tương tự
- Inheritance là một trong 3 nguyên tắc cơ bản của lập trình hướng đối tượng. Khi cần xây dựng một lớp dựa trên một hoặc nhiều lớp hay interface có sẵn bạn chỉ cần khai báo lớp mới kế thừa lớp cũ và có thể viết thêm những method, attribute của mình tùy thích. Tuy nhiên sẽ có lúc tính kế thừa của OOP sẽ không sử dụng được. Thật sự là có nhiều trường hợp như vậy mà tôi đã từng gặp phải. Giả sử bạn cần mở rộng một lớp nhưng lớp đó là lớp seal (không cho kế thừa) thì inheritance chắc chắn không sử dụng được rồi. Một ví dụ khác là khi bạn cần kết hợp nhiều thứ có sẵn để làm ra một lớp mới sử dụng lại những thứ đó. Như các bạn biết C# và Java không hỗ trợ đa kế thừa, chúng ta không thể nào kế thừa từ 2 lớp có sẵn được. Như vậy nếu bạn đã có một lớp để tìm ước số chung lớn nhất của hai số tự nhiên, một lớp để cộng hai phân số, và khi dùng adapter để kết hợp hai thứ có sẵn đó bạn sẽ làm được một lớp có khả năng cộng hai phân số với nhau với kết quả phép cộng là phân số đã tối giản.
- Như vậy, Adapter Pattern là một design pattern, nó còn được gọi là Wrapper Pattern biến đổi một lớp không thể sử dụng trực tiếp được thành một lớp mới có thể sử dụng. Trong một số trường hợp, Adapter Pattern còn được sử dụng để biến đổi dạng dữ liệu. Để tiết kiệm chỗ trong và giảm tính phức tạp trong database, nhiều giá trị string có thể được lưu thành một string duy nhất với kí tự phân cách đặc biệt, mẫu Adapter sẽ chịu trách nhiệm tách các string đó ra thành array để nơi khác có thể sử dụng. Nhưng nói chung Adapter Pattern được sử dụng đúng nhất khi biến cái không thể thành cái có thể.
II/ Sử dụng Adapter Pattern như thế nào?
- Theo tôi nói ở trên, Adapter Pattern thường được dùng để biến cái không thể thành cái có thể, biến lớp không thể sử dụng thành một lớp mới có thể sử dụng. Tuy nhiên, Adapter Pattern còn được dùng để biến những lớp có sẵn thành một thứ khác dễ và tiện sử dụng hơn. Vì vậy có hai cách thiết kế và áp dụng Adapter Pattern trong lập trình, ở một số tài liệu Design Pattern, hai cách này được gọi là Object Adapter Pattern và Class Adapter Pattern. Một số trang còn gọi là Inheritance-Way và Composition-Way. Nói chung những cách gọi này đều dựa trên cách thiết kế lớp Adapter. Object Adapter Pattern hay Composition-Way là cách sử dụng một đối tượng thuộc về lớp chúng ta muốn biến đổi bên trong lớp Adapter. Trong khi đó, ClassAdapterPattern hay Inheritance-Way là cách kế thừa từ lớp chúng ta muốn biến đổi hoặc mở rộng thêm.
III/ Ví dụ mẫu về Adapter Pattern
- Tôi sẽ lần lượt đưa 2 ví dụ thiết kế lớp về 2 cách này:
A. Object Adapter Pattern (Composition-Way)
- Để kiếm đủ tiền ăn nhậu, massage
B. Class Adapter Pattern (Inheritance-Way)
- Tôi đã rất mừng khi phát hiện ra rằng chỉ mất chút ít thời gian để làm cái BarChart. Nhưng khi đưa cho khách hàng, họ yêu cầu tôi phải sửa lại cái BarChart, phải cho người dùng thấy được số liệu khi rê chuột lên hình. Đúng là đồ free có khác, không khi nào dùng ngay được. Tôi đành phải thiết kế lại lớp ThoaiChart theo hướng khác. Bây giờ tôi sẽ inherit lớp CloseSourceCode.BarChart và override lại hàm hover chuột của nó. Thiết kế lớp như sau:
IV/ So sánh Adapter Pattern với các Design Patterns khác?
- Trong các Design Pattern thuộc nhóm Structural Design Pattern, có rất nhiều mẫu có thiết kế lớp tương tự nhau. Chúng ta nên nắm rõ ý nghĩa và trường hợp sử dụng của chúng để không nhầm lẫn và … để khi em trai em gái nó hỏi còn biết đường trả lời
• Với khả năng biến cái không thể thành có thể, Adapter Pattern làm cho 1 lớp có thể sử dụng được sau khi lớp đó đã được thiết kế xong trong khi Bridge Pattern làm điều đó trước khi lớp này được tạo ra.
• Bridge Pattern được nghĩ ra với ý tưởng tách rời những abstraction ra khỏi những implementation, và tạo tính độc lập cao giữa chúng. Trong khi đó, Adapter pattern đươc dùng để làm những lớp không liên quan (không cùng namespace, khác interface) có thể làm việc với nhau.
• Adapter Pattern về ý nghĩa sẽ biến đổi interface của một lớp, Proxy Pattern sẽ không đổi interface của một lớp và Decorator Pattern sẽ kế thừa và mở rộng dựa trên interface đó.
• Adapter Pattern khi dùng sẽ thay đổi interface của một đối tượng có sẵn để biến nó thành thứ có thể dùng được. Decorator Pattern sẽ phát triển thêm trên một đối tượng mà không làm thay đổi interface của nó
• Giống với Adapter Pattern, Façade Pattern có thể kết hợp nhiều xử lý vào một lớp mới đơn giản và gọn hơn , nhưng nó sẽ định nghĩa mới 1 interface trong khi Adapter Pattern tái sử dụng một interface (Trong ví dụ về các biểu đồ ở trên thì interface được tái sử dụng là IMyChart, không hề có một interface mới được tạo ra)
- Các bạn có thể tham khảo thêm những ví dụ code mẫu cho Adapter Pattern ở trang wiki. Ví dụ thứ hai ở trang này là biến đổi 1 lớp DList thành Stack dựa trên những xử lý có sẵn của DList mà không cần viết lại những xử lý phức tạp cho lớp Stack.
Tài liệu tham khảo:
Một câu chuyện về Bridge Pattern
Posted On Friday, May 30, 2008 at at 5:16 PM by Unknown- Mẫu thiết kế Bridge được thiết kế với ý tưởng tách rời những xử lý của một lớp ra lớp khác, từ đó có thể dễ dàng biến đổi hoặc thay thế mà không làm ảnh hưởng đến những nơi có sử dụng lớp ban đầu. Điều này có thể hiểu như sau: bạn thiết kế một lớp với rất nhiều xử lý, bây giờ bạn không muốn để những xử lý đó trong lớp của bạn nữa, bạn sẽ tạo ra một lớp mới và move toàn bộ những xử lý đó sang lớp mới. Khi đó trong lớp cũ sẽ giữ một đối tượng thuộc về lớp mới, và đối tượng này sẽ chịu trách nhiệm xử lý thay cho lớp ban đầu. Tại sao chúng ta làm như vậy và cách thực hiện như thế nào? Hi vọng với bài viết này chúng ta tìm được lời giải thích cho 2 câu hỏi trên.
- Mẫu Bridge về khía cạnh nào đó khá giống với mẫu Adapter ở chỗ: người ta sẽ nhờ vào một lớp khác để thực hiện một số xử lý nào đó. Tuy nhiên, ý nghĩa và mục đích sử dụng của hai mẫu thiết kế này hoàn toàn khác nhau. Mẫu Adapter Pattern hay còn gọi là Wrapper pattern được dùng để biến đổi một lớp/interface sang một dạng khác có thể sử dụng được. Adapter Pattern giúp nhiều lớp có thể làm việc với nhau dễ dàng mà bình thường không thể. Một trường hợp tôi gặp phải và có thể áp dụng Adapter Pattern là khi tôi không thể kế thừa lớp A nhưng muốn làm một lớp B có những xử lý tương tự như lớp A. Khi đó tôi làm lớp B như sau, các xử lý của B sẽ gọi những xử lý của A khi cần
Hình 1: Ví dụ về mẫu Adapter
- Đó chỉ là một trong nhiều cách sử dụng có thể có của mẫu Adapter, tôi sẽ nói thêm về Adapter trong một bài viết khác, ở đây chỉ nêu ra ý nghĩa khác nhau của hai mẫu thiết kế này. Trước khi đi vào câu chuyện của mẫu Bridge, chúng ta hãy xem sơ đồ lớp của mẫu Bridge:
Hình 2: Sơ đồ lớp của mẫu Bridge
Câu chuyện về mẫu thiết kế Bridge (Bridge Pattern)
- Có một người đã làm nghề đầu bếp đã 20 năm. Ông ta đã đi rất nhiều nơi trên thế giới, làm việc cho rất nhiều nhà hàng khác nhau và đã học được cách nấu rất nhiều món ngon. Không những thế ông ta còn sáng tạo ra những món đặc biệt cho riêng mình nữa. Nhờ tài nghệ của ông, các nhà hàng nơi ông làm luôn luôn đông khách. Sau 20 năm nhìn lại, ông thấy đã đến lúc mình phải mở nhà hàng cho riêng mình. Đã để dành được một số vốn kha khá, ông quyết định chọn một trong những thành phố năng động của Việt Nam là TPHCM để bắt đầu sự nghiệp riêng. Thế là nhà hàng của ông ta ra đời và kinh doanh khá phát đạt nhờ vào danh tiếng và tài nghệ của mình. Ông ta dành hết tâm huyết để tự tay nấu mọi món ngon nhất cho các thực khách vì không tin tưởng vào các phụ bếp được thuê. Càng ngày danh tiếng của nhà hàng càng được nhiều người biết đến, không chỉ những người sành ăn trong nước mà cả những du khách nước ngoài cũng tìm đến nhà hàng này để thưởng thức khi có cơ hội ghé đến TPHCM. Ông ta rất vui mừng trước doanh thu ngày càng cao nhưng bên cạnh đó cũng rất lo cho sức khỏe của mình, vì ông phải đứng chế biến món ăn từ 6h sang đến 11h đêm hằng ngày
- Các con của ông rất thương bố mình và khuyên ông nên truyền nghề lại cho các học trò, các người ấy sẽ giúp ông làm việc cho nhà hàng. Suy nghĩ khá lâu, ông thấy rằng các con ông nói rất có lý. Ông ta chọn 1 đứa học trò ngoan và giỏi nhất truyền toàn bộ những gì mình biết. Sau khi đã lĩnh hội được toàn bộ khóa học, đứa học trò đã phụ ông nấu nướng cho nhà hàng, nhờ vậy ông được rảnh rổi về sớm đi tập thể dục thẩm mỹ và làm những việc quản lý, mở rộng kinh doanh.
- Thời gian trôi qua, ông muốn mở thêm một nhà hàng mới ở HN. Cũng trong giai đoạn này, ông nhận được email của một số khách ruột bảo rằng chất lượng món ăn bắt đầu kém đi, không đậm đà như trước đây. Sau khi tìm hiểu, ông phát hiện nguyên nhân là do thằng học trò đểu cáng đang âm mưu mở nhà hàng riêng nên nó lơ là việc nấu nướng
Hình 3: Bản kế hoạch mở 2 nhà hàng mới của ông
// Bridge pattern -- Structural example
using System;
namespace nthoai.blogspot.com.BridgePattern
{
// "Abstraction"
class NhaHang
{
protected DauBep _dauBep;
// Property
public DauBep DauBep
{
set { _dauBep = value; }
}
public virtual void CheBienMonAn()
{
_dauBep.CheBienMonAn();
}
}
// "Implementor"
abstract class DauBep
{
public abstract void CheBienMonAn();
}
// "Nhà hàng món Huế"
class NhaHangMonHue : NhaHang
{
public override void CheBienMonAn()
{
_dauBep.CheBienMonAn();
}
}
// "Nhà hàng món miền Nam"
class NhaHangMienNam : NhaHang
{
public override void CheBienMonAn()
{
_dauBep.CheBienMonAn();
}
}
// "ConcreteImplementorA"
class DauBepMonAnHue : DauBep
{
public override void CheBienMonAn()
{
Console.WriteLine("Đây là những món ăn Huế do đầu bếp Huế thực hiện");
}
}
// "ConcreteImplementorB"
class DauBepMonAnMienNam : DauBep
{
public override void CheBienMonAn()
{
Console.WriteLine("Đây là những món ăn miền Nam do đầu bếp miền Nam thực hiện");
}
}
// Ông đầu bếp mở nhà hàng như sau
class ÔngĐầuBếpGià
{
static void Main()
{
NhaHang _nhaHangMonHue = new NhaHangMonHue();
// Set implementation and call
_nhaHangMonHue.DauBep = new DauBepMonAnHue();
_nhaHangMonHue.CheBienMonAn();
NhaHang _nhaHangMonMienNam = new NhaHangMienNam();
// Change implemention and call
_nhaHangMonMienNam.DauBep = new DauBepMonAnMienNam();
_nhaHangMonMienNam.CheBienMonAn();
// Wait for user
Console.Read();
}
}
}
Hình 4: Kế hoạch mở nhà hàng :D
- Câu chuyện của ông đầu bếp già là một ví dụ về cách sử dụng của Bridge khi đưa những xử lý sang một lớp khác. Trong các tài liệu về mẫu Bridge, các tác giả thường sử dụng các thuật ngữ như: Abstraction, Implementor, RefinedAbstraction và ConcreteImplementor. Cá nhân tôi thấy những từ vô cùng chuyên môn như vậy hơi khó hiểu, tôi thích những ví dụ thực tế hơn, tuy nhiên những từ ấy hoàn toàn chính xác. Sử dụng mẫu Bridge trong lập trình không chỉ đơn giản là đưa những xử lý của một lớp sang lớp khác rồi gọi nó từ lớp mới, mọi việc không chỉ đơn giản như vậy. Ý tưởng trên chỉ là một vế của Bridge Pattern, ở đây chúng ta chưa nói đến vế: “từ đó có thể dễ dàng biến đổi hoặc thay thế mà không phải ảnh hưởng đến những nơi có sử dụng lớp ban đầu” (so you can vary or replace the implementation without changing the client code). Xin thưa với các bạn đây chỉ là cách diễn giải của tôi, không phải khái niệm hay định nghĩa tiếng Việt của Bridge Pattern cho nên nếu các bạn muốn xem nguồn tiếng Anh hãy chịu khó google. Quay lại chỗ “biến đổi, thay thế mà không ảnh hưởng”, tôi đã có viết về chuyện này ở loạt bài về Dependency Injection của Spring.NET. Trong trường hợp của mẫu Bridge, chúng ta có thể giảm sự phụ thuộc giữa các lớp với nhau bằng cách sử dụng Interface hoặc abstract class. Trong các tài liệu về Bridge Pattern, các Abstraction và Implementer là những lớp abstract hoặc interface. Trong khi đó, các RefinedAbstraction và ConcreteImplementor là những thể hiện cụ thể hay những lớp con của các abstract class/ interface nói trên. Như vậy trong câu chuyện ông đầu bếp của chúng ta thì Abstraction là NhàHàng, RefinedAbstraction chính là Nhà Hàng Món Huế, Nhà Hàng Món Nướng Nam Bộ, Nhà Hàng Châu Âu còn ConcreteImplementor là các đầu bếp: Đầu Bếp Món Âu, Đầu Bếp Món Huế, …
- Như vậy chữ Bridge (chiếc cầu) ở đây là quan hệ “use/have” giữa Abstraction và Implementation, giữa Nhà Hàng và Đầu Bếp. Một RefinedAbstraction sẽ không khai báo cụ thể một ConcreteImplementor nào đang được sử dụng mà chỉ biết nó đang có 1 Implementor nào đó; khi mở một Nhà Hàng mới ta sẽ mướn Đầu Bếp (biết nấu ăn), còn cụ thể đầu bếp nấu được món miền nào sẽ quyết định lúc chọn xong địa điểm
- Trong một số trường hợp, mẫu thiết kế Bridge được dùng như một cầu nối giữa 2 nhóm đối tượng khác nhau, giữa nhóm các dữ liệu và nhóm các cách thể hiện ra màn hình, đó là ví dụ thường được dùng trong các bài viết về mẫu Bridge. Như chúng ta biết có rất nhiều chuẩn hình ảnh như JPG, PNG, BMP, GIF, … và các hệ điều hành khác nhau như Windows, MacOS, UNIX đều hiểu và có thể hiển thị các định dạng ảnh này.
- Cấu trúc hình ảnh và cách hiển thị chúng là hai thành phần quan trọng của một định dạng ảnh. Cấu trúc hình chính là cách mà hình đó được lưu trữ, và cách thể hiện chúng sẽ tương đối khác nhau trên các chương trình xem hình trên các hệ điều hành khác nhau. Trong trường hợp này các file hình với các định dạng khác nhau sẽ thường xuyên thay đổi, và các hệ điều hành cần vẽ hình cũng nhiều tương tự. Nếu được yêu cầu thiết kế lớp cho bài toán trên sao cho tính tái sử dụng cao nhất, bạn sẽ làm thế nào. Như đã nói, có rất nhiều chuẩn hình, mỗi hình sẽ có những thông tin riêng của nó và mỗi hình nên được thiết kế để có khả năng tự vẽ nó (Information Expert) . Nhưng vì cách vẽ sẽ khác nhau trên các hệ điều hành khác nhau nên nếu chúng ta thiết kế lớp hình tự thực hiện hàm vẽ rồi khi cần thì sửa hàm vẽ để nó hoạt động được trên hệ điều hành khác là không nên. Bridge Pattern sẽ giúp ta đưa những hàm vẽ đó sang những lớp khác để vẽ trên các hệ điều hành tương ứng. Nhờ vậy, khi một chuẩn hình mới được tạo ra hay có một thuật toán vẽ khác được nghĩ ra, sẽ rất dễ dàng để ta code thêm một lớp vẽ mới cho chương trình.
- Một ví dụ khác cũng tương tự là khi bạn cần xây dựng một ứng dụng thể hiện ra màn hình thông tin khác nhau với những loại user khác nhau. Cùng một dữ liệu nhưng nếu là Admin bạn sẽ cho phép hiển thị chi tiết hơn, có thêm những button cho phép edit hoặc delete chẳng hạn. Trong khi đó user thường hoặc guest chỉ có thể xem mà không thể làm gì khác. Còn rất nhiều trường hợp khác mà chúng ta có thể dùng Bridge cũng như có nhiều mẫu thiết kế tương tự Bridge như Strategy, Adaptor. Nếu chúng ta nắm được ý nghĩa, sự khác nhau và khi nào nên áp dụng những thiết kế này của 4 lão tiền bối thì công việc lập trình sẽ hứng thú, đầy tính sáng tạo và không nhàm chán chút nào. Hi vọng đến đây chúng ta đã tìm được câu trả lời cho 2 câu hỏi trên. Tôi sẽ bàn thêm một chút về những lợi ích và trở ngại khi sử dụng Bridge:
Benefits in using Bridge Pattern
1/ Đặt vấn đề: chúng ta có một lớp A, và các lớp A1, A2, A3 kế thừa từ lớp A. Các lớp A1, A2, A3 sẽ được thừa hưởng các attribute và behavior của lớp A. Như vậy vô tình các xử lý của lớp A1, A2, A3 sẽ y như A trừ khi chúng phải override lại. Trong chương trình, khi cần sử dụng một trong các lớp A1, A2, A3 người ta thường khai báo chung chung là A thay vì khai báo cụ thể là ta sẽ gọi mày đó A1, A2, A3. Cách sử dụng lớp A ta gọi là abstraction hay là trừu tượng hóa và những xử lý của lớp A mà các A1, A2, A3 thừa hưởng gọi là implementation. Sử dụng thiết kế Bridge sẽ giúp chúng ta giảm sự phục thuộc giữa abstraction và implementation. Tính kế thừa trong hướng đối tượng thường gắn chặt abstraction và implementation lúc build chương trình. Bridge Pattern có thể được dùng để cắt đứt sự phụ thuộc này và cho phép chúng ta chọn implementation phù hợp lúc runtime.
2/ Giảm số lượng những lớp con không cần thiết. Một số trường hợp sử dụng tính inheritance sẽ tăng số lượng subclass vô tội vạ. Ví dụ trường hợp chương trình xem hình trên các hệ điều hành khác nhau, ta có 6 loại hình và 3 hệ điều hành. Sử dụng inheritance trong trường hợp này sẽ làm ta thiết kế 18 lớp trong khi áp dụng Bridge sẽ giảm số lượng lớp xuống 9
3/ Lợi ích thứ 2 dẫn đến một hệ quả: Code sẽ gọn gàn hơn và dẫn đến kích thước ứng dụng sẽ nhỏ hơn.
4/ Các Abstraction và Implementation của nó sẽ dễ dàng thay đổi lúc runtime cũng như khi cần thay đổi thêm bớt trong tương lai. Như vậy chương trình của chúng ta cũng sẽ dễ bảo trì hơn.
5/ Tương tự như lợi ích 4, nếu sử dụng Bridge, hệ thống sẽ dễ mở rộng về sau. Đây hầu như là một yêu cầu bắt buộc đối với các chương trình lớn. Nhiều công ty sẽ cùng làm trong từng giai đoạn phát triển. Một số công ty phần mềm ở VN thường làm outsource: maintain hoặc mở rộng một framework hay một chương trình đã được làm sẵn ở nước ngoài. Công ty khách hàng sẽ yêu cầu chúng ta thêm module cho ứng dụng có sẵn nhưng không được sửa đổi framework/ứng dụng có sẵn của họ vì các framework/ứng dụng đó có thể được công ty nâng cấp lên version mới. Đó là bài toán các bạn sẽ gặp phải khi làm ở những project trung bình và lớn, và ta sẽ vô cùng bực mình và cảm thấy khó khăn khi framework/chương trình đó được đám lập trình viên ở đâu đâu code vô cùng chuối giờ lại bắt mình viết thêm vào
6/ Những nơi cần sử dụng các abstraction sẽ hoàn toàn độc lập với các implementation.Ví dụ các lớp khác của chương trình xem ảnh sẽ độc lập với thuật toán vẽ ảnh trong các implementation. Như vậy ta có thể update chương trình xem ảnh khi có một thuật toán vẽ ảnh mới mà không cần phải sửa đổi nhiều thậm chí build lại toàn chương trình nếu có dùng các kĩ thuật Dependency Injection.
1 Drawbacks in using Bridge Pattern
- Khi sử dụng Bridge Pattern, chúng ta đã tăng số lần gọi gián tiếp lên hai. Trong ví dụ trên, với cách làm cũ, lớp Image sẽ thực hiện hàm vẽ của chính nó. Nếu áp dụng Bridge, hàm vẽ sẽ được gọi thông qua một lớp implementation tương tứng như vậy có thêm một lần khởi tạo đối tượng và gọi đối tượng, có thêm một lần hủy đối tượng và lượng bộ nhớ bị chiếm để chạy chương trình sẽ tăng lên một chút. Theo tôi những bất lợi này vô cùng không đáng kể so với những lợi ích mang lại khi chúng ta áp dụng Design Pattern nói chung và Bridge Pattern nói riêng.
-----------------------------------------------------------------
- Cuối cùng, ông đầu bếp già của chúng ta sẽ không có thời gian đi massage hay đi chơi gôn nếu cứ tiếp tục tự nấu ăn. Nhà hàng của ông sẽ rất khó khăn khi đầu bếp chính nghỉ việc nếu không có nhiều đầu bếp giỏi chuyên môn. Thử tưởng tượng trong thực tế chúng ta mua một chiếc xe gắn máy mà khi hư thì phải bỏ đi và mua xe mới thay vì có phải tìm phụ tùng thay thế. Tính độc lập, chuyên môn hóa giữa các bộ phận trong một hệ thống luôn được đề cao trên mọi lĩnh vực trong cuộc sống. Riêng trong thiết kế phần mềm, design pattern là những bài học kinh nghiệm mà khi áp dụng tốt sẽ biến người lập trình thành một nghệ sĩ. Một lần nữa, tôi hi vọng bài viết này không chỉ giúp các bạn biết Bridge Pattern là gì mà còn hiểu được tại sao và khi nào cần nó. Tóm lại, cách sử dụng inheritance một cách máy móc có thể vô tình dán chặt những abstraction và implementation với nhau trong khi một số ứng dụng cần điều ngược lại. Bridge Pattern có thể được sử dụng khi một abstraction có nhiều implementation và cả 2 nhóm có thể có nhiều thay đổi mà không phụ thuộc hoặc ảnh hưởng gì nhóm còn lại
Các tài liệu tham khảo:
Bridge Design Pattern in C# and VB.NET
http://www.informit.com/articles/article.aspx?p=30297
http://www.c-sharpcorner.com/UploadFile/rajeshvs/BridgePattern
http://en.wikipedia.org/wiki/Bridge_pattern
http://www.codeguru.com/cpp/cpp/cpp_mfc/patterns/article.php/c817/
http://en.wikipedia.org/wiki/Wrapper_pattern
http://www.dofactory.com/Patterns/PatternAdapter.aspx