Dot Net Remoting Overview

.NET Remoting là gì?

- Trước hết .NET Remoting là một kĩ thuật .NET được giới thiệu từ .NET framework 1.1. Cùng với .NET Webservice, .NET remoting là lựa chọn cho giải pháp xử lý tính toán từ xa. .NET Remoting là một kĩ thuật cho phép một đối tượng này truy xuất đến một đối tượng khác nằm ở các Application Domain khác nhau. Và nếu giải thích theo kiểu bình dân, ta có thể sử dụng .NET Remoting đế gọi một chương trình hoặc một service chạy trên một máy vi tính khác để xử lý một cái gì đó và trả kết quả tính toán lại cho ta.

.NET Remoting Overview

Hình 1: .NET Remoting Overview

.NET Remoting và Distributed COM

- Vào năm một ngàn chín trăm hồi đó :bbpcuoi3:, người ta thường thực hiện việc giao tiếp giữa các process bằng cách sử dụng Distributed COM hay còn gọi là DCOM. DCOM đã rất hữu ích cho những chương trình chạy trên các máy tính cùng loại và nằm trong cùng một mạng. Tuy nhiên, DCOM trở nên lỗi thời vì nó không thể chạy trên Internet. DCOM dựa trên một tập giao thức mà không phải object nào cũng hỗ trợ và điều này khiến DCOM không chạy được trên những platform khác nhau. Ngoài ra, DCOM sử dụng nhiều port trong khi các port ấy thường bị chặn bởi firewall. Tất nhiên mở những port đó để nó hoạt động được không khó nhưng đó là một trong những phiền phức. :bbptuc:

- .NET Remoting khắc phục những yếu kém của DCOM bằng cách hỗ trợ nhiều giao thức khác nhau.

.NET Remoting và Web Services

- Về khía cạnh xử lý từ xa thì Web Services hoàn toàn tương tự như .NET Remoting. Thậm chí người ta có thể làm cho .NET Remoting trở thành 1 Web Services bằng cách host nó trong IIS. Web Services cho phép các ứng dụng có thể giao tiếp với nhau mà không phụ thuộc platform, ngôn ngữ lập trình, … Tuy nhiên Web Services là một môi trường “stateless”, có nghĩa là nó không lưu lại bất kì trạng thái gì của lần gọi trước và nó cũng không biết gì về phía client đang thực hiện request. Client và server Web Services chỉ có thể trao đổi với nhau bằng các thông điệp SOAP. Những điều sau đây là các điểm khác nhau chính giữa .NET Remoting và Web Serices, chúng cũng là những nhân tố để ta chọn lựa giữa 2 công nghệ này:

• ASP.NET Web Services chỉ có thể được truy xuất qua HTTP còn .NET Remoting có thể được dùng trên nhiều giao thức khác nhau như TCP, HTTP.

• Web Services là một môi trường stateless. Khi có một request từ phía client, sẽ có một object mới được tạo ra để thực hiện request đó trên server. Còn .NET Remoting lại hỗ trợ nhiều lựa chọn state management và có thể thực hiện nhiều request từ một client, đồng thời có hỗ trợ callbacks.

• Web Services serialize các đối tượng thành XML bên trong SOAP message và vì thế có thể truyền tải thông tin của bất cứ thành phần nào miễn có thể chuyển thành XML. Còn đối với .NET Remoting thì tùy giao thức và định dạng message mà nó có thể truyền đi thông tin như thế nào. Ngoài ra theo như giới thiệu thì .NET Remoting có cho phép đối tượng được truyền vào theo cả kiểu tham chiếu(reference) và tham trị (value)

• Web services có thể hoạt động trên các platform môi trường khác nhau trong khi .NET Remoting yêu cầu phía clients phải là .NET application. :bbpraroi:


Channels

- Trong kĩ thuật .NET Remoting thì Channel được hiểu như là một kênh để giao tiếp giữa client và server. Một object từ client sẽ thông qua Channel để giao tiếp với object phía server, Channel sẽ truyền tải những message từ hai phía. Như giới thiệu phía trên thì có hai channel chính là TcpChannel và HttpChannel tương ứng với các giao thức TCP và HTTP. Ngoài ra, TcpChannel và HttpChannel đều có khả năng extend thành những Custom Channel của bạn.
Làm sao để tạo một Object có thể Remote được trong .NET Remoting?

- Một Object remote được chỉ là một object thông thường nhưng phải được inherit từ MarshalByRefObject. Đoạn code sample ở hình 2 là một ví dụ đơn giản về Remotable Object. Đối tượng SampleObject trong hình có một method đơn giản trả về phép tính tổng của hai số nguyên. Giá trị trả về của hàm là kiểu số nguyên, kiểu built-in của .NET framework. Nếu bạn muốn trả về kiểu dữ liệu bạn tự định nghĩa, hoặc một instance của class bạn định nghĩa thì lớp đó của bạn phải được khai báo với attribute Serializable.

.NET Remoting Overview

Hình 2: Remotable Object Sample
Các bạn đang xem bài viết về .NET Remoting từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

Tạo chương trình Server để host Remotable Object

- Kế tiếp, chúng ta cần tạo ra một chương trình server để lắng nghe những request từ phía client. Trong ví dụ này chúng ta sẽ sử dụng TCP/IP channel. Đầu tiên chúng ta tạo một instance channel và đăng kí một port tương ứng cho nó. Khi có một Request từ phía client, server sẽ nhận request đó và Remote Object của chúng ta sẽ thực thi Request này. Trong .NET Remoting, có hai cơ chế để tạo instance của Remote Object rồi từ đó thực thi request: Singleton và Singlecall. Tùy vào mục đích sử dụng, nhu cầu của chương trình mà server của bạn có thể khai báo theo cơ chế WellKnownObjectMode.SingleCall, hay WellKnownObjectMode.Singleton. Khi khai báo Singleton, Remote Object sẽ được sinh ra, thực thi request, reply lại phía client và sau đó, object này vẫn được lưu lại chứ không bị hủy đi. Đến khi nào process chạy chương trình server kết thúc thì instance này mới bị trình hốt rác Garbage Collector hốt đi. Và ngược lại, khi khai báo là SingleCall, Remote Object sẽ được khởi tạo và hủy đi đối với mỗi lần nhận request từ phía client, cơ chế này tương tự như mô hình .NET Web Service truyền thống.:bbpraroi:

- Nếu bạn muốn sử dụng .NET Remoting trong IIS thì không cần tạo một chương trình server như thế này. Và tất nhiên, IIS chỉ hỗ trợ HttpChannel. Nếu host 1 .NET Remoting bên trong IIS bạn sẽ mặc nhiên sử dụng được cơ chế Authentication của IIS, ngược lại nếu làm một chương trình server để host như trên thì bạn phải cài đặt cơ chế Authentication của riêng mình. Để host một Remote Object bên trong IIS, trước tiên phải tạo 1 Virtual Directory cho application, sau đó đặt đoạn code đăng kí service bên trong event Application_Start (file global.asax) :bbpxtay:

- Trong ví dụ này, chúng ta sẽ không sử dụng IIS mà sẽ tạo một console application. Có nhiều lựa chọn khi không sử dụng IIS, ta có thể sử dụng console application, Win form application nhưng trong thực tế, người ta sẽ sử dụng một Windows Service để làm. Còn Console application hay Winform Application thường chỉ dùng để minh họa :D. Trong ví dụ này, chúng ta sẽ sử dụng port 9999 cho may mắn. Có thể một chương trình nào đó trong máy của bạn đã sử dụng port này, nếu bị như vậy bạn phải chọn port khác. Và sau cùng, để kiểm tra xem máy bạn đang lắng nghe trên những port nào (port nào đã bị sử dụng) thì ta dùng lệnh “netstat –a” trong command prompt.

- Còn bây giờ, hãy xem một console application project với 1 class tên là SampleSerrver. Trong project này tôi đã thêm reference tới System.Runtime.Remoting vào trong project để nó có thể chạy được.

.NET Remoting Overview

Hình 3: Sample Server host Remotable Object
Các bạn đang xem bài viết về .NET Remoting từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

Tạo chương trình client để sử dụng Remote Object.

- Chương trình client trong ví dụ này cũng khá đơn giản, nó sẽ connect vào server, tạo một instance của Remote Object và excute method tính tổng.

- Các bạn lưu ý rằng trong cả chương trình client và chương trình server đều phải reference tới class SampleObject. Client sẽ gọi method của instance SampleObject, nhưng server sẽ thực thi xử lý đó chứ không phải phía client.

.NET Remoting Overview

Hình 4: Sample Client Application
Các bạn đang xem bài viết về .NET Remoting từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

Test thử chương trình

- Trước tiên chạy chương trình server, bạn sẽ thấy message “Press the enter key to exit” trong cửa sổ console. Như vậy server của bạn đang lắng nghe trên port 9999. Bây giờ bạn hãy chạy chương trình client và sẽ nhìn thấy kết quả trả về trên màn hình. Bạn có thể chạy nhiều client để cùng request đến 1 server nhưng không thể chạy nhiều server. Bạn có thể copy chương trình server sang một máy của bạn mình và nhờ chạy thử, còn bạn sửa lại chương trình client, sửa “localhost” thành IP của máy bạn mình và chạy thử để thấy kết quả.

Tóm tắt:

- Ví dụ ở trên đã sử dụng code C# để khai báo các cấu hình cho server và client tuy nhiên .NET Remoting cho phép ta cấu hình trước trong file config (App.config). Các bạn có thể tham khảo một số resource phía dưới để biết cách làm.

- .NET Remoting là một trong những kĩ thuật tiện lợi cho những chương trình dạng Distributed Computing. Cách sử dụng nó phức tạp hơn Web Service tuy nhiên nếu bạn muốn tăng performance thì .NET Remoting với Singleton và TCP channel sẽ là lựa chọn rất tốt.

- Với sự ra đời của .NET Framework 3.x, Bác Bill và đồng bọn đã giới thiệu nền tảng mới hơn cho các kĩ thuật RPC, đó là WCF mạnh hơn .NET Remoting rất nhiều. (To be continued) :bbpnen:

Code sample : Download

The Open-Closed Principle

- 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 :

NHỮNG THÀNH PHẦN CỦA PHẦN MỀM (CLASSES, MODULES, FUNCTIONS, …)
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.:bbpraroi:

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?:bbpnghi:

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.

The Open-Closed Priciple

Hình 1: Closed Client
Các bạn đang xem bài dịch về nguyên tắc Open-Closed trong thiết kế hướng đối tượng từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Hình 2 thể hiện một ví dụ tương tự nhưng tuân theo nguyên tắc open-closed. Trong trường hợp này, lớp AbstractServer là một lớp abstract với các member functions được khai báo virtual rõ ràng. Lớp Client và lớp abstract này vẫn có quan hệ “uses”. Tuy nhiên các đối tượng kiểu Client sẽ sử dụng những đối tượng thuộc về các lớp con của lớp AbstractServer. Nếu chúng ta muốn các đối tượng kiểu Client sử dụng đối tượng thuộc kiểu server khác nữa thì chỉ cần tạo lớp mới dẫn xuất từ lớp Abstract Server. Và vì thế lớp Client có thể không cần thay đổi.

The Open-Closed Priciple
Hình 2: Open Client

Các bạn đang xem bài dịch về nguyên tắc Open-Closed trong thiết kế hướng đối tượng từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)


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. :bbpcuoi3:Nếu muốn mở rộng function này để nó có thể vẽ được các hình mới, buộc lòng phải sửa lại code.

- 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.:bbpbuon:

- 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.
Các bạn đang xem bài dịch về nguyên tắc Open-Closed trong thiết kế hướng đối tượng từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

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?:bbpnen: Phải viết code như thế nào bên trong lớp Circle để nó biết rằng Circle nên được vẽ trước Squares. Hãy xem thử Hình 4:

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. :bbpquau:

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.:bbpbuon:

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ì.
Các bạn đang xem bài dịch về nguyên tắc Open-Closed trong thiết kế hướng đối tượng từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

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.:bbpcuoi5:


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.:bbpxtay:

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.

Fedora on Virtual Server 2005 R2 SP1

- Hiện nay có rất nhiều phần mềm hỗ trợ làm máy ảo như VMWare, Virtual Box, Virtual PC và Virtual Server. Virtual Box là phần mềm free hỗ trợ đa dạng các hệ điều hành đang rất được ưa chuộng vì tính dễ sử dụng và miễn phí. Trong khi đó Virtual Server 2005 R2 SP1 của MicroSoft tuy cũng miễn phí nhưng hỗ trợ ít hơn với các hệ điều hành Linux. Tui đã thử install Fedora 9 trên Virtual Server 2005 R2 SP1 (Máy host là Windows XP Pro SP2) và gặp một chút khó khăn. Một lý do mà tui thích sử dụng Virtual Server hơn những phần mềm khác vì nó có thể điều khiển trên giao diện web. Không biết Fedora cài xong trên Virtual Server 2005 sẽ được tự động cấu hình network thế nào, hi vọng nó cùng dải ip với máy host của tui để tiện share file :D. Ở thời điểm viết bài này tui vẫn cài chưa xong Fedora 9 trên Virtual Server. Nếu sử dụng Virtual Box, có thể các bạn sẽ gặp khó khăn khi chia sẽ file giữa máy ảo và máy thật. Virtual Box hỗ trợ một shared folder để bạn chia sẽ file, nhưng những ai muốn sử dụng samba để mở windows shared folder thì có thể gặp khó khăn vì Virtual Box tự động cấu hình network cho máy ảo Linux với IP 10.0.0.x. Điều chỉnh network cho Fedora/Ubuntu tui ko rành lắm, tui toàn làm trên GUI, tui đã thử cách tạo 1 host interface từ Virtual Box rồi chỉnh thành Bridge Network để máy ảo và máy thật cùng dải IP. Nói chung là cũng hơi phức tạp và để làm được các bước trên đều phải cài Virtual Linux Addition. Tìm hiểu trên mạng thì thấy rằng bác Bill và đồng bọn đã hỗ trợ Linux trên Virtual Server 2005 R2 SP1 nên tui quyết định cài thử và bài viết này sẽ ghi lại các bước để cài đặt thành công Fedora trên đồ chơi của Microsoft.:bbpraroi:

-Các thứ cần thiết trước khi bắt đầu:

  • Một đĩa DVD Fedora9 hoặc 1 file DVD Iso của Fedora9. Các bạn có thể download từ Torrent hoặc từ host của Fedora Project. (hơn 3GB một chút). Tui sử dụng file ISO download vì làm biếng đi mua đĩa :bbpbuon:
  • Máy còn dư khoảng 10GB, RAM available khoảng 256MB :bbpskien:
  • Vài giờ vì Fedora cài các pakage khá lâu.:bbpcuoi3:

Bước 1: Mở Virtual Server Administrator Website và tạo Virtual Machine.

Install Fedora on Virtual Server 2005 R2 SP1

- Những lần trước khi cài Fedora9 trên Virtual Box, tui mở System Monitor và thấy Fedora chỉ sử dụng khoảng 150MB cho những cài đặt mặc định. Nên tui nghĩ mình chọn 256MB bộ nhớ chắc cũng đủ. Vì Linux cài trên HDD bus SCSI nên các bạn nhớ chọn bus SCSI cho phần HDD. Ngoài ra khi cài với các lựa chọn mặc định thì Fedora chiếm cỡ 7GB, nên mình chọn size cho HDD cỡ 7GB trở lên cho lành. Cuối cùng, phần Virtual Network Adapter nhớ chọn card mạng physical của bạn.

Bước 2: Mount ISO Fedora9 file.

Install Fedora on Virtual Server 2005 R2 SP1

- Sau khi tạo xong máy ảo, bạn click vào phần CD/DVD để điều chỉnh đĩa cài đặt. Nếu bạn có DVD Fedora9 thì chọn Phyical CD/DVD và chọn ổ đĩa DVD và đưa đĩa cài đặt vào. Còn nếu sử dụng file ISO giống tui thì chọn như hình và copy, paste cái đường dẫn đến file ISO của bạn.

Bước 3: Chuẩn bị cài đặt.

- Mọi thứ chuẩn bị đã xong, bạn hãy start máy ảo lên, màn hình welcome quen thuộc hiện ra :D. Nếu bạn ENTER để install mặc định thì có thể sẽ không cài được, máy ảo sẽ reset liên tục nên bạn hãy bấm phím TAB và theo bước 4.

Install Fedora on Virtual Server 2005 R2 SP1

Các bạn đang xem bài viết về cài đặt Fedora9 trên Virtual Server 2005 R2 SP1 từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

Bước 4: Bắt đầu cài đặt

- Sau khi bấm TAB để Edit Option, sẽ có một dòng command line cho các bạn tùy biến. Hãy gõ vào: noreplace-paravirt theo như hình, chú ý khoảng trắng giữa img và noreplace.

Install Fedora on Virtual Server 2005 R2 SP1

Bước 5: Chờ đợi và hi vọng.

- Đến đây các bước cài đặt sẽ bình thường. Nếu không cúp điện đột suất thì có thể sẽ cài đặt xong trong vòng 2h. Hê hê good luck.:bbpbtay:

Install Fedora on Virtual Server 2005 R2 SP1
Các bạn đang xem bài viết về cài đặt Fedora9 trên Virtual Server 2005 R2 SP1 từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)



- Hiện nay, Bác Bill và các đồ đệ chỉ mới support RedHat và SuSE nên cho dù tui đã cố cài các Virtual Machine Addition lên trên Fedora Guest OS nhưng nó vẫn chạy không mượt mà. Có lẽ thời điểm hiện nay sử dụng Virtual Box cho Fedora/Ubuntu vẫn là lựa chọn số 1. Muahaha xin lỗi vì làm mất thời gian cài của các bạn :bbpchoc:

Posted in Labels: , , | Post a comment 2 comments
rss
 

About Me

Place I've live
Near Bossley Park, Sydney, NSW, Australia
Place I've work
  • Freelancer (from 06/2010 to present)
  • Harvey Nash (from 05/2008 to 06/2010)
  • DataDesign Vietnam (10/2005 to 04/2008)
Place I've studied
  • University of Natural Science (Bachelor of Science HoChiMinh City Vietnam From 2001 to 2005)
  • Le Hong Phong High School (HoChiMinh City Vietnam From 1997 to 2000)