Viết ASP.NET bằng MVP và NHibernate phần 4 - Castle Windsor
Posted On Friday, October 31, 2008 at at 9:20 PM by UnknownBài viết này được dịch, tóm tắt và bổ sung dựa vào bài viết NHibernate Best Practices with ASP.NET, 1.2nd Ed trên code Project. Các code sample trong bài dựa vào database Northwind của Microsoft và tham khảo 99,99% từ code mẫu của tác giả Billy McCafferty.
I. Giới thiệu Inversion of Control và Castle Windsor
I.1 Giới thiệu
- Có rất nhiều website và blog nói về Inversion of Control và Dependency Injection. Có nguồn cho rằng đây là hai khái niệm khác nhau (MSDN) nhưng sự phụ Martin Fowler thì cho rằng Dependency Injection là tên gọi khác của Inversion of Control để khỏi confused. Tui thì thấy giải thích của bác Martin Fowler khá ổn, dù gì cũng là người lớn tuổi nên chắc không lừa con nít tụi mình
Inversion of Control (IoC) có nghĩa rằng các object không phải tự nó tạo ra các objects mà nó phụ thuộc. Thay vào đó, nó sẽ có những objects nó cần nhờ vào một bên thứ ba.
- Còn Inversion of Control Container là một library dựa trên nguyên tắc của IoC để hỗ trợ chúng ta trong việc tạo và hủy các đối tượng trong chương trình. Khi nhắc đến Dependency Injection, ngoài Spring.NET có lẽ người ta sẽ nghĩ đến Castle Windsor. Castle Windsor là một Container mà phần chính của nó là Castle MicroKernel (cũng là 1 container). Theo như Castle Prolect thì Windsor Container là một library dựa trên library MicroKernel nhưng có hỗ trợ thêm khả năng config uyển chuyển và một số tính năng khác. Nói cách khác, Windsor Container tiện dụng hơn MicroKernel và đối với những ai đã sử dụng Spring.NET thì sẽ thấy rằng Windsor cách dùng đơn giản như đang giỡn
Hình : Lâu đài Windsor ở Anh Quốc
I.2 Cách dùng Windsor
- Trước tiên hãy nghĩ xem Castle Windsor giúp chúng ta giải quyết problem gì. Người ta thường phân chia chương trình thành các module, các layer hay các tầng, tầng này khai báo Interface và tầng kia sẽ có những lớp implement các interface này. Như vậy với cách làm thông thường chắc chắn bằng cách nào đó sẽ có sự phụ thuộc, sẽ có các dòng code đại loại như : IOrderDao orderDao = new NHibernateOrderDao(). Và như vậy rõ ràng nơi nào sử dụng dòng code này đã có sự phụ thuộc vào module/layer chứa lớp NHibernateOrderDao. Người ta muốn tránh sự phụ thuộc này bằng cách thay dòng code có chữ new bằng một cách khác và cách người ta sử dụng chính là Dependency Injection. Có 3 khái niệm Dependency Injection: Constructor injection, Setter Injection và Interface Injection nhưng cái chúng ta thường gặp nhất là Interface Injection. Trong phần này ta sẽ tìm hiểu cách sử dụng Windsor Conainer và Interface Injection.
- Để khỏi phải new 1 object NHibernateOrderDao như trên, ta sẽ sử dụng Castle Windsor. Ta tạo 1 file config tên là Windsor.config rồi add vào project, nội dung của file này như sau:
<configuration>
service="MyProjectName.Core.DataInterfaces.IOrderDao, MyProjectName.Core">
<parameters>
<nameOfParameter2>100</nameOfParameter2>
</parameters>
</component>
</components>
</configuration>
Code 1: Nội dung file config cho Castle Windsor
- Bước thứ hai, mình mở mở file config và thêm vào như sau:
<configuration>
</configSections>
<castle>
</castle>
</configuration>
Code 2: Thêm config cho Windsor vào file app/web.config
- Theo như config trên thì file app/web config và file Windsor.config phải ở cùng thư mục với nhau. Trong Code1, ta khai báo một component với id là orderDao (có thể đặt tên gì cũng được), trong đó type và service lần lượt là Interface và class implement tương ứng. Trong phần parameters, ta khai báo các value cần thiết cho constructor của lớp NHibernateOrderDao. Và cuối cùng trong chương trình, ta sẽ dùng code sau đây để lấy được instance của class NHibernateOrderDao như mong muốn:
IOrderDao = (IOrderDao)windsorContainer.Resolve("orderDao");
// Or : IOrderDao = (IOrderDao)windsorContainer.Resolve(typeof(IOrderDao));
Code 3: Lấy instance của class NHibernateOrderDao như mong muốn
II. Tạo Web project và cấu hình Windsor, NHibernate
II.1 Load config cho Windsor trong global.asax
- Sau khi tạo một project Web, add file Windsor.config và sửa file web.config như đã nói ở trên xong thì điều tiếp theo ta sẽ nghĩ xem nên load cái config ấy như thế nào. Rõ ràng không nên cứ load config này mỗi lần muốn Resolve 1 object. Tương tự như khi sử dụng log4net cho web application, chúng ta phải load config log4net 1 lần duy nhất và code cho hành động này được đặt ở file global.asax. Nhưng khác với log4net, chúng ta cần một cách nào đó để giữ lại instance của WindsorContainer nên không thể thực hiện trong Global.asax được. Cách của bác Billy rất hay, đó là viết 1 lớp CustomHttpApplication, đặt tất cả các cấu hình cần thiết vào Application_Start, kế đến khai báo một public property để giữ instance của Windsor Container trong suốt thời gian sống của Application và vì thế instance của Windsor Container có thể được sử dụng bất cứ nơi nào trong tầng Web, cụ thể là các Page và các UserControl:
public static IWindsorContainer WindsorContainer
{
get { return windsorContainer; }
}
public void Application_Start(object sender, EventArgs e) {
// Initialize log4net
XmlConfigurator.Configure();
// Create the Windsor Container for IoC.
// Supplying "XmlInterpreter" as the parameter tells Windsor
// to look at web.config for any necessary configuration.
windsorContainer = new WindsorContainer(new XmlInterpreter());
}
public void Application_End(object sender, EventArgs e) {
windsorContainer.Dispose();
}
Code 4: Giữ instance của WindsorContainer trong HttpApplication
II.2 Open Session In View Principle
- Nếu google cụm từ trên thì các bạn sẽ thấy rất nhiều topic viết về chủ đề này trong đó tui thấy nên đọc nhất là trang của Hibernate.org. Vấn đề chúng ta mong muốn giải quyết ở đây là hạn chế số lần NHibernate mở connection đến database. Trong một view bất kì (page/usercontrol) có thể ta sẽ cần load nhiều dữ liệu, gọi nhiều query để load nhiều thông tin khác nhau để hiển thị trên form. Nếu với cách làm bình thường mỗi lần cần 1 Dao object nào đó select dữ liệu ta lại để NHibernate mở một connection thì performance của chương trình sẽ rất thấp, bởi vậy người ta nghĩ ra ý tưởng chỉ mở 1 connection ứng với mỗi View. Kĩ thuật của tác giả là khi có một request đến một view, một connection hay còn gọi là NHibernate session sẽ được tạo ra nếu chưa có, sau khi được sử dụng session NHibernate này sẽ được lưu trong 1 Hashtable, và nếu trong view đó tiếp tục còn Request khác thì session NHibernate này sẽ được lấy ra để tiếp tục sử dụng. Cuối cùng khi không còn Request nào nữa, ứng với sự kiện EndRequest của 1 view thì NHibernate session này sẽ được lấy ra lần cuối để close đi, sau đó empty Hashtable. Vậy nếu trên 1 trang aspx của ta có 3 usercontrol tương ứng 3 view sẽ có 3 lần mở connection đến database mà thôi thay vì mỗi usercontrol bản thân nó lại mở cả đống connection. Vậy làm sao để NHibernate Session chỉ mở một lần ứng với 1 View? Người ta có thể dùng HttpModule để thực hiện mục đích này.
- Như chúng ta biết HttpModule được khai báo trong file web.config và cho phép chúng ta implement những function bổ xung cho web application. Một HttpModule sẽ được gọi ngay vào đầu và sau khi có request đến 1 View bất kì. Thông thường người ta sử dụng HttpModule để phục vụ việc Logging hay check Security, trong trường hợp này ta đã implement lớp NHibernateSessionModule(Xem bài 3) để quản lý việc đóng và commit transaction. Lớp này được đặt trong project ProjectBase.Data và Pattern này có thể được sử dụng trong nhiều prolect khác nhau của bạn:
/// Commits and closes the NHibernate session provided by the supplied <see cref="NHibernateSessionManager"/>.
/// Assumes a transaction was begun at the beginning of the request; but a transaction or session does
/// not *have* to be opened for this to operate successfully.
/// </summary>
private void CommitAndCloseSession(object sender, EventArgs e) {
OpenSessionInViewSection openSessionInViewSection = GetOpenSessionInViewSection();
try {
// Commit every session factory that's holding a transactional session
foreach (SessionFactoryElement sessionFactorySettings in openSessionInViewSection.SessionFactories) {
if (sessionFactorySettings.IsTransactional) {
NHibernateSessionManager.Instance.CommitTransactionOn(sessionFactorySettings.FactoryConfigPath);
}
}
}
finally {
// No matter what happens, make sure all the sessions get closed
foreach (SessionFactoryElement sessionFactorySettings in openSessionInViewSection.SessionFactories) {
NHibernateSessionManager.Instance.CloseSessionOn(sessionFactorySettings.FactoryConfigPath);
}
}
}
Code 5: Implement 1 lớp HttpModule để apply Open In View Principle
- Và đây là một phần của file web.config để khai báo HttpModule này:
<httpModules>
</httpModules>
</system.web>
Code 6: Khai báo HttpModule trong web.config
III. Cải tiến DaoFactory bằng Generic và Castle Windsor
- Hiện tại có một vấn đề với DaoFactory của chúng ta. Khi cần tạo mới một class XXXDao nào đó cho một entity mới, chúng ta phải mở code, sửa lại interface IDaoFactory và thêm vào một dòng IXXXDao GetXXXDao(). Tất nhiên chúng ta cũng phải sửa lại lớp NHibernateDaoFactory, implement thêm method GetXXXDao(). Như vậy khá là bất tiện. Bác huynguyen_fisherman (đồng nghiệp, PM của project NHAU
1/ Refactoring lại interface IDao, thay tên của nó thành IGenericDao:
Hình : Refactor lại interface IDao
2/ Thêm vào một interface rỗng IDao và để IGeneric Dao inherit từ interface IDao này
3/ Sửa lại interface IDaoFactory như sau:
Code 7: Sửa lại interface IDaoFactory
4/ Implement lại class NHibernateDaoFactory:
{
public NHibernateDaoFactory()
{
}
// id is "component id" that we "declare" in the CastleComponents.config
public T GetDao
{
Check.Require(string.IsNullOrEmpty(id) == false, "component id cannot be null or empty");
return (T)_windsorContainer.Resolve(typeof(T));
}
private IWindsorContainer _windsorContainer = new WindsorContainer(new XmlInterpreter());
}
Code 8: Sửa lại class NHibernateDaoFactory
5/ Và đây là cách sử dụng để lấy 1 Dao object như ý muốn
ICustomerDao customerDao = daoFactory.GetDao<ICustomerDao>("CustomerDao");
Code 9: Cách lấy một Dao bằng NHibernateDaoFactory
IV. Tóm tắt & Kết luận
- Castle Windsor là một IOC Container rất được ưa thích, nếu các bạn xem ở blog này http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx sẽ thấy rằng Windsor xếp đầu bảng và ngoài nó ra còn rất nhiều library Dependcy Injection nữa. Trong số đó thì có Unity và NInjection là hai IoC Container tui muốn tìm hiểu khi có thời gian :D.
- Trong bài này ta đã tìm hiểu cách áp dụng Generic và IoC Container để làm cho code gọn gàng, dễ sửa đổi. Chúng ta chỉ mới tạo project web và chuẩn bị trước một số thứ như config NHibernate và Windsor cũng như làm quen với Open In View Principle. Trong phần tiếp theo chúng ta sẽ tìm hiểu cách áp dụng pattern MVP để làm các trang aspx. Cách làm mới có gì khác so với cách làm truyền thống? Hãy chờ hồi sau sẽ rõ
Code phần 4: http://nthoaiblog.googlepages.com/EnterpriseSample-part4.zip
Các đoạn code minh họa trong bài viết được tui rút gọn cho dễ hiểu, code được implement cuối cùng trong demo sẽ có nhiều điểm khác biệt...
(Còn tiếp)
Tham khảo:
http://martinfowler.com/articles/injection.html
http://msdn.microsoft.com/en-us/library/aa973811.aspx
http://www.castleproject.org/container/documentation/v1rc3/concepts/ioc.html
http://en.wikipedia.org/wiki/Inversion_of_control
http://sourceforge.net/forum/message.php?msg_id=2847509
http://www.hibernate.org/43.html
http://www.builderau.com.au/program/dotnet/print.htm?TYPE=story&AT=339284537-339028399t-320002019c
http://dotnetslackers.com/articles/designpatterns/InversionOfControlAndDependencyInjectionWithCastleWindsorContainerPart1.aspx
http://dotnetslackers.com/articles/designpatterns/InversionOfControlAndDependencyInjectionWithCastleWindsorContainerPart2.aspx
Dependency Injection with Spring.Net p6
Posted On Friday, May 23, 2008 at at 10:32 PM by Unknown(Bài viết dịch từ bài Dependency Injection with Spring.Net của David Consdorf, thêm hành thêm tiêu rồi nấu lại bởi Nguyễn Thoại )
- Testing with Dependency Injection
- Một điểm thú vị nữa của Spring.NET là chúng ta có thể sử dụng Unit Test để test các component của chúng ta rất dễ dàng. Với khả năng thay đổi mối liên kết giữa các thành phần trong ứng dụng, người lập trình có thể tạo ra những “đối tượng giả” để test, vì vậy ta có thể test đoạn code họ đang viết mà không cần lo lắng đến những phần chưa hoàn thành của hệ thống. Điều này có thể lạ với một số bạn vì khái niệm unit testing không được dạy ở các trường đại học, đến khi đi làm ở một số công ty các bạn sẽ thường xuyên gặp khái niệm này. Ý tưởng cơ bản là những hàm, những đoạn code các bạn viết ra đều phải có một phần code khác để test ngay những hàm bạn tạo. Giả sử bạn viết một lớp tính điểm trung bình các môn Toán, Lý, Hóa của học sinh, trong lớp (class) của bạn sẽ gọi đến một lớp Utility tính trung bình của một người khác trong nhóm viết. Vấn đề bạn gặp phải là lớp Utility đó chưa được viết xong, như vậy không thể nào test được. Nhưng rất may là đã có Spring.NET sẽ giúp chúng ta gỡ rối vấn đề này.
- Một ví dụ khác, khi bạn đang viết một số lớp dịch vụ (service – BLL) cho ứng dụng nhưng tầng dữ liệu (DAL) vẫn chưa hoàn thành; hoặc có khi bạn đang viết phần mềm cho một khách hàng nước ngoài rất khó tính và họ không cho bạn truy cập vào server database của họ; với Spring.NET và Dependency Injection, bạn vẫn có thể test thử các lớp dịch vụ bằng cách tạo ra các Mock objects để điền dữ liệu chưa lấy được từ database mà không cần đến những đối tượng ở tầng dữ liệu, hay nói cách khác bạn cũng không cần đến database để test. Mock là một kĩ thuật lí thú để test ứng dụng rất linh động và tôi hi vọng có dịp sẽ chia sẽ những hiểu biết của mình với các bạn ở những bài viết sau. Trong phạm vi bài viết này tôi chỉ có thể giới thiệu Mock như là một framework giúp chúng ta tạo ra những object giả với những hàm hoạt động theo cách mà ta muốn. Trong trường hợp này nó sẽ tạo ra những object giả ở tầng dữ liệu và giả vờ như đã kết nối với database và trả về dữ liệu cho những lớp ở tầng dịch vụ. Sau này, khi chúng ta cần kết nối tầng dịch vụ với tầng dữ liệu, bạn chỉ cần thay đổi file cấu hình của Spring.
- Ngoài những điều kể trên, có một số kĩ thuật khác rất lí thú mà Spring.NET hỗ trợ bao gồm cả hệ thống test của chính Spring.NET. Vì vậy nếu bạn muốn tìm hiểu hãy đọc những bài viết khác liên quan đến vấn đề này ở trang web www.springframework.net
- More Spring.NET
- Conclusion
- Code Examples
- About the Author
- Work Experience
- Education
- About the Translator
: Click here
(Các bạn đang xem phần 6)
Dependency Injection with Spring.Net p5
Posted On at at 4:12 PM by Unknown(Bài viết dịch từ bài Dependency Injection with Spring.Net của David Consdorf, thêm hành thêm tiêu rồi nấu lại bởi Nguyễn Thoại )
- Như ở giới thiệu phần trước, trong phần này chúng ta sẽ xem một ví dụ khác về cách áp dụng phổ biến của dependency injection khi chương trình được tách thành nhiều tầng và các tầng này được kết nối với nhau bằng Spring.NET
Quick N-Tier Example
- Bất cứ ai từng làm việc với những ứng dụng khá lớn có thể đã biết đến kiến trúc N-tier. Ý tưởng cơ bản là ứng dụng được chia thành nhiều tầng chức năng. Mỗi tầng đảm nhiệm một chức năng nào đó và chỉ liên lạc với những tầng ở phía trên hoặc dưới nó. Ví dụ dễ gặp nhất là kiến trúc 3 tầng (lúc đi học được dạy là mô hình 3 lớp) như sau:
Hình 12: Mô hình 3 lớp
- Trong ví dụ này, tầng trên cùng là tầng giao diện (presentation/view) làm nhiệm vụ hiển thị dữ liệu trên trang web, trên một ứng dụng client, một ứng dụng di động, … Kế tiếp, ta có một tầng dịch vụ (Business Logic Layer – BLL hay Service layer) làm nhiệm vụ xử lý tính toán những logic của ứng dụng. Và cuối cùng là tầng tầng dữ liệu (DAL - Data Access Layer) chịu trách nhiệm giao tiếp với cơ sở dữ liệu (database), XML hoặc bất kì loại datasource nào mà chương trình có thể sử dụng. Mỗi tầng chỉ giao tiếp duy nhất với tầng khác thông qua một interface, mỗi tầng sẽ không biết các tầng khác hoạt động như thế nào và được lập trình như thế nào.
- Ví dụ tầng giao diện sẽ gọi tầng dịch vụ để tạo ra một business object để giữ những gì người dùng nhập vô, sau đó tầng dịch vụ sẽ tạo ra một đối tượng dựa trên những gì nhập vào, tính toán gì đó nếu cần thiết và tiếp tục gọi tầng dữ liệu để lưu trữ những thông tin này. Tầng dịch vụ không cần quan tâm tầng giao diện đang hiển thị dữ liệu trên trang web hay trên một windows application, nói cách khác nó không quan tâm ứng dụng thuộc loại nào, và nó cũng chẳng cần quan tâm làm thế nào tầng tầng dữ liệu lưu thông tin xuống dưới, nó cũng không quan tâm ứng dụng đang sử dụng loại cơ sở dữ liệu nào. Những cách làm này giúp chúng ta dễ dàng thay đổi chương trình để thích hợp cho nhiều loại người dùng khác nhau, trên desktop, trên web hoặc trên thiết bị di động đều được. Thêm vào đó khi có cần thay đổi loại cơ sở dữ liệu, chúng ta chỉ cần viết lại hoặc thay thế (nếu có sẵn) tầng dữ liệu. Nếu có một tầng nào đó cần thay đổi thì chỉ có tầng đó phải sửa đổi và những tầng khác có thể được sử dụng lại
- Spring.Net và Dependency Injection giúp chúng ta xây dựng những ứng dụng nhiều tầng bằng cách chia ứng dụng ra thành nhiều component. Với Spring.Net, chúng ta có thể giữ cho những components trong 1 tầng độc lập với những tầng khác và có thể inject dependency vào những tầng khác dễ dàng nhờ vào file cấu hình xml.
Các bạn hãy tham khảo đoạn code ví dụ cách liên kết một đối tượng từ tầng dịch vụ với một đối tượng từ tầng tầng dữ liệu:
public interface IServiceExample {
void doOperationOnObject(Object obj);
Object getObject(int objectID);
void saveObject(Object obj);
}
public class ServiceExample : IServiceExample {
public IDAOExample _daoExample;
public ServiceExample(IDAOExample daoExample) {
_daoExample = daoExample;
}
public void doOperationOnObject(Object obj) {
// Enter business logic for operation here
}
public Object getObject(int objectID) {
// Use DAO to retrieve object from the database
return _daoExample.getObject(objectID);
}
public void saveObject(Object obj) {
// Use DAO to save object to the database
_daoExample.saveObject(obj);
}
}
public interface IDAOExample {
Object getObject(int objectID);
void saveObject(Object obj);
}
public class DAOExample : IDAOExample {
public DAOExample() {
}
public Object getObject(int objectID) {
// Put database code to retrieve object here
}
public void saveObject(Object obj) {
// Put database code to save object here
}
}
Hình 13: Services & DAO objects
<object name="ServiceExample" type="ServiceExample, __Code" singleton="true">
<constructor-arg name="daoExample" ref="DAOExample" />
</object>
<object name="DAOExample" type="DAOExample, __Code" singleton="true">
</object>
Hình 14: Services & DAO mappings
- Trong ví dụ trên, bạn có một đối tượng thuộc tầng dịch vụ giao tiếp với một đối tượng thuộc tầng dữ liệu để lưu thông tin của một object xuống database. Đối tượng DAO được nhúng vào bên trong đối tượng tầng dịch vụ nhờ Spring.NET. Bằng cách này, sẽ không có sự liên kết cụ thể nào trong code giữa các tầng (layers).
- Nếu một ngày đẹp trời nào đó khách hàng yêu cầu bạn đổi từ MySQL sang sử dụng Oracle, tất cả những gì bạn cần làm là tạo mới những lớp DAO cho Oracle, sau đó sửa file config của Spring.NET sang Oracle DAO object. Không có dòng nào trong code của tầng dịch vụ (BLL) cần phải thay đổi hay build lại.
- Bạn cũng nên chú ý là trong trường hợp này, tầng giao diện (Presentation) khi sử dụng những đối tượng dịch vụ cũng có thể nhờ Spring.NET để lấy; cũng tương tự như những đối tượng ở tầng dịch vụ sử dụng Spring.NET để gọi các đối tượng ở tầng dữ liệu. Một điều cần chú ý nữa là các đối tượng ở tầng dịch vụ và tầng dữ liệu đều là singletons bởi vì chúng được sử dụng để tính toán và lưu trữ trong chương trình, chúng chẳng cần giữ lại dữ liệu gì cả. Hiện nay, có rất nhiều framework .NET hỗ trợ các bạn sẵn tầng DAL với các chức năng có sẵn như lazy load, caching, optimized queries,… Các bạn chỉ cần viết một số store procedure hoặc config một chút xíu là có thể sử dụng được mà không cần viết code nhiều. Các framework đó cũng rất linh hoạt khi cần phải chuyển đổi loại database. Hiện có mốt số framework rất nổi tiếng như Ibatis, NHibernate, Active Record, LinQ,… tùy vào nhu cầu và thói quen bạn có thể lựa chọn framework thích hợp nhất cho mình. Nếu người là người cổ điển có thể NHibernate thích hợp với bạn, còn nếu là dân sành điệu thích thời trang thì LinQ là lựa chọn tốt
.Các bạn có thể tìm hiểu thêm thông tin về các Object-relational mapping (O/R Mapping) framework từ đây, theo tôi đó là một trong những bài viết rõ ràng súc tích và dễ hiểu nhất.
Dependency Injection with Spring.Net p4
Posted On Thursday, May 22, 2008 at at 4:47 PM by Unknown(Bài viết dịch từ bài Dependency Injection with Spring.Net của David Consdorf, thêm hành thêm tiêu rồi nấu lại bởi Nguyễn Thoại )
- Trong phần 3, tôi đã sử dụng một ví dụ khác với các ví dụ truyền thống để giải thích mẫu thiết kế Strategy Pattern. Nếu bạn muốn tìm hiểu thêm những ví dụ khác, các bạn có thể tham khảo bài viết của tác giả Dadiv Consdorf ở đây hoặc trên wiki. Như đã nói ở bài viết trước, mẫu thiết kế Strategy Pattern sẽ giúp bạn trừu tượng hóa (abstract) những xử lý (behavior) tương tự nhau của đổi tượng(object) bằng cách đưa những xử lý đó ra những lớp độc lập, và những lớp này sẽ cùng implement một interface.
- Ở những ví dụ truyền thống, các bài viết thường sử dụng bài toán về các thuật toán sắp xếp để minh hoạ mẫu thiết kế Strategy. Trong đó, một lớp NumberArray sẽ có khả năng sắp xếp danh sách các số bằng nhiều thuật toán khác nhau. Các thuật toán này sẽ được đóng gói trong các lớp Sort và chính những lớp BubbleSort, HeapSort,… này lại implement cùng 1 interface ISortStrategy. Lớp NumberArray không sử dụng một thuật toán cụ thể nào để sắp xếp, nó sẽ giữ một object thuộc về kiểu ISortStrategy và các thuật toán sort sẽ được chuyển đổi dễ dàng trong lúc chương trình chạy và ngoài ra nếu sử dụng Spring.NET ta có thể thay đổi những lớp Strategy bằng file config.
- Quay lại ví dụ ở bài trước, các bạn sẽ thấy sự tương tự. Nếu xem lớp “ThuBayTuanToi” là chương trình chính thì các bạn sẽ thấy lớp NguyenThoai có thể chọn cách DiXemPhim hoặc DiAnToi một cách dễ dàng bằng cách khởi tạo với một đối tượng thuộc kiểu tương ứng. Tuy nhiên, việc này sẽ làm mất đi tính loose coupling của chương trình vì lớp “NguyenThoai” phải reference đến những lớp DiXemPhim, DiAnToi. Nói cách khác nó phải biết DiXemPhim là như thế nào, DiAnToi là như thế nào. Spring.NET sẽ giúp tôi giải quyết vấn đề đó, Spring.NET sẽ giữ tất cả những chiến thuật đi chơi của tôi, và NguyenThoai với Spring.NET chỉ cần thoả thuận với nhau các kí hiệu : “ĐiĂn”, “XemPhim”, “Cafe”,... Bản thỏa thuận như sau:
<object name="DiAnToi" type="DiAnToi, __Code" singleton="true">
</object>
<object name="DiXemPhim" type="DiXemPhim, __Code" singleton="true">
</object>
<object name="DiUongCafe" type="DiUongCafe, __Code" singleton="true">
</object>
<object name="XemPhim" type="NguyenThoai, __Code" singleton="false">
<constructor-arg name="cachDiChoi" ref="DiXemPhim" />
</object>
<object name="DiAnToi" type="NguyenThoai, __Code" singleton="false">
<constructor-arg name="cachDiChoi" ref="DiAnToi" />
</object>
<object name="Cafe" type="NguyenThoai, __Code" singleton="false">
<constructor-arg name="cachDiChoi" ref="DiUongCafe" />
</object>
Hình 8: Mapping các objects trong file config.
- Như vậy ít nhất tôi với Spring.NET đã thoả thuận với nhau 3 mật hiệu, nếu cần đi chơi theo cách nào thì thôi chỉ nói cho Spring.NET biết mật hiệu và Spring sẽ lấy ra những thứ tôi cần. Cách làm như trên giúp chúng ta thay đổi các chiến thuật cần sử dụng trong lúc runtime. Còn việc map đối tượng chính với các lớp cài đặt các behavior đã được định nghĩa trong file config như hình 8. Trong phần 5 của loạt bài này, chúng ta sẽ nói thêm một ví dụ nữa về ứng dụng Spring.NET để map các đối tượng với nhau. Còn bây giờ, chúng ta hãy xem tôi sẽ sử dụng Spring như thế nào trong lần đi chơi thứ bảy tuần tới :
public class ThuBayTuanToi {
public void main() {
IApplicationContext ctx = ContextRegistry.GetContext()
NguyenThoai _thoai = (NguyenThoai)ctx.GetObject("XemPhim");
_thoai.Money = 100.000 VND;
_thoai.Vehicle = new Vehicle("Xe Bus");
_thoai.DanBanGaiDiChoi();
}
}
Hình 9: Sử dụng Spring.NET để lấy đối tượng được map.
- Một điều quan trọng nữa nên nói đến là việc sử dụng mẫu thiết kế Singleton bên trong Spring.NET. Bạn có thấy rằng ở những ví dụ trước đều có một property là singleton ở phần object mapping trong các file config. Theo tôi, Singleton là mẫu thiết kế đơn giản nhất, thông dụng nhất và thường được sử dụng nhất. Để hiểu rõ hơn về thiết kế này, các bạn hãy xem sơ đồ lớp bên dưới:
Hình 10: Sơ đồ lớp của mẫu Singleton.
- Theo hình thì chúng ta thấy lớp singleton có một hàm GetInstance() để trả về instance của object Singleton. Tuy nhiên tôi thích cách sử dụng Public Property hơn :
public class Singleton {
private static Singleton _instance = null;
protected Singleton() {
}
public static Singleton Instance
{
get
{
if ( _instance == null )
_instance = new Singleton();
return _instance;
}
}
}
Hình 11: Ví dụ mẫu Singleton.
- Mẫu thiết kế singleton giúp chúng ta bảo đảm rằng lúc nào cũng tồn tại nhiều nhất 1 instance của một lớp tương ứng. Như các bạn thấy trong sơ đồ lớp, chúng ta sẽ làm được chuyện đó bằng cách thiết kế một lớp với 1 protected constructor (hàm khởi tạo), và vì là protected nên nó chỉ có thể tạo ra instance bên trong. Chúng ta nên sử dung protected ở đây mà không dung private vì khi khai báo là protected chúng ta có thể sử dụng unit test để test và những lớp con có thể kế thừa constructor này. Với cách làm như vậy, không một chỗ nào trong chương trình có thể tạo ra instance của lớp. Do đó, sẽ chỉ có 1 instance duy nhất của đối tượng được tạo ra và đối tượng đó sẽ tồn tại trong xuốt quá trình chạy của chương trình.
- Lớp singleton sẽ phải cung cấp một public static Property hoặc một hàm static với kiểu trả về là chính lớp đó để các đối tượng khác có thể truy xuất được object mà nó đóng gói bên trong. Mấu thiết kế này có vẻ như rất giống với cách chúng ta sử dụng những hàm static, nhưng nó khác biệt ở chỗ singleton cho chúng ta sự linh động hơn trong lập trình vì chúng ta vẫn có một instance thực sự của một đối tượng để làm việc. Ngoài ra có một số điểm khác biệt khác mà tôi sẽ đề cập đến trong bài viết khác. Việc áp dụng mẫu singleton đúng chỗ đúng lúc trong ứng dụng rất có lợi, chương trình của bạn sẽ chạy nhanh hơn vì không cần phải liên tục tạo ra những đối tượng mới và cũng không cần huỷ những đối tượng sau khi đã sử dụng xong. Ngoài ra cũng sẽ tiết kiệm được bộ nhớ của máy tính vì không cần phải lưu trữ nhiều đối tượng trong hệ thống. Ngày nay, lợi ích thứ hai này không thể hiện rõ vì bộ nhớ máy tính của chúng ta không ngừng được tăng lên với giá ngày càng rẻ, nhưng theo tôi tối ưu hoá ứng dụng khi có thể là không bao giờ thừa nhất là khi chúng ta áp dụng design pattern
- Spring.NET đã sử dụng nguyên tắc này của mẫu singleton bằng cách cho phép người dùng quyết định xem 1 lớp có duy nhất một instance hay không. Vì vậy, trong ví dụ trên, lớp NguyenThoai không cần phải là singleton, bởi vì sao? Bởi vì thường thì tình hình tài chính và tâm trạng tôi hoàn toàn khác nhau ở mỗi ngày thứ 7. Tôi sẽ có nhiều lần đi chơi khác nhau và với mỗi lần ấy có thể tôi sẽ có số tiền khác nhau. Ngoài ra tôi là một người rất chung thuỷ, tôi thích đến những chỗ cũ, ăn ở những quán ăn cũ nên theo tôi những lớp DiAnToi, DiXemPhim, … nên là những lớp singleton. Ngoài nhiệm vụ thực hiện quá trình đi chơi và biến IsHappy của tôi thành true thì nó chẳng nên có vai trò gì khác. Tương tự, ở ví dụ của tác giả với những thuật toán sort, những lớp BubleSort, HeapSort, QuickSort,... cũng nên là singleton vì ngoài chuyện sắp xếp một mảng các số, nó chẳng cần giữ giá trị sau khi sắp xếp của mảng làm gì. Do đó mình chỉ cần khởi tạo một lần và sử dụng suốt cho những lần sau.
- Hi vọng kết thúc phần 4 này, các bạn có thể hiểu được đôi chút về Spring.NET, hiểu được Spring có thể giúp các bạn làm gì và vì sao nên làm như vậy. Nói chung khi thiết kế lớp cho chương trình, nếu bạn gặp phải tình huống một lớp A có giữ 1 instance của lớp B thì bạn đang đối mặt với tính loose coupling. Lúc đấy, bạn nên nghĩ xem sau này có khi nào cần phải làm một lớp B2 tương tự lớp B hay không. Nêú có, chính là lúc bạn cần nghĩ đến Dependency Injection và Spring là một trong những lựa chọn để giải quyết bài toán của bạn. Để giúp bạn hiểu rõ hơn về khả năng mapping objects của Spring, ở phần 5, tác giả sẽ nói đến một ví dụ khác lý thú hơn, thông dụng và thực tế hơn trong việc áp dụng Spring.NET để kết nối các đối tượng với nhau.
(Các bạn đang xem phần 4)
Dependency Injection with Spring.Net p3
Posted On Tuesday, May 20, 2008 at at 3:37 PM by Unknown(Bài viết dịch từ bài Dependency Injection with Spring.Net của David Consdorf, thêm hành thêm tiêu rồi nấu lại bởi Nguyễn Thoại )
Contstructer Injection vs. Property Injection
- Trong ví dụ trước, chúng ta đã sử dụng kĩ thuật “constructor injection” để set giá trị cho 1 property của đối tượng được Spring tạo ra. Thật ra có một cách khác để làm chuyện này, Spring cũng hỗ trợ chúng ta set giá trị cho object gọi là “property injection”. Các bạn hãy xem cách chúng ta cấu hình trong file xml:
<object name="SimpleMessageByProperty" type="SimpleMessage, __Code" singleton="false">
<property name="Message" value="Hello, I am a simple message
set by using property-based dependency injection." />
<object>
Hình 5: Simple Message by Property Object Mapping
- Trong hình trên, chúng ta không thấy thẻ <constructor-arg> như ở file xml trong bài trước. Điều này có nghĩa là chúng ta sẽ không sử dụng constructor có arg để thiết lập giá trị cho đối tượng mà giá trị đó được set một cách trực tiếp thông qua public property. Điểm khác nhau duy nhất trong code khi chúng ta cần tạo ra lớp SimpleMessage là là thay vì sử dụng giá trị string “SimpleMessage” để tạo la instance của lớp SimpleMessage, ta sẽ sử dụng string “SịmpleMessageByProperty” như phần định nghĩa trong file xml ở trên.
- Nói chung, tôi vẫn thích sử dụng cách “constructor injection” hơn bởi vì nó giúp chúng ta thấy rõ cách đối tượng được thiết lập. Tuy nhiên, có nhiều trường hợp buộc chúng ta phải sử dụng property injection. Vấn đề là chúng ta nên chọn loại injection nào để sử dụng mà thôi.
Reference Injection Example with the Strategy Pattern.
- Tuy ví dụ về SimpleMessage ở phần 2 là một ví dụ tốt về mapping một đối tượng, nhưng nó không thể hiện được sức mạnh của Spring.Net. Set giá trị cho một property chẳng có gì đặc biệt cả. Vì vậy, chúng ta hãy xem 1 ví dụ lý thú nữa: inject một reference đến đối tượng khác trong một đối tượng và chúng ta sẽ kết hợp kĩ thuật này với một ví dụ về mẫu thiết kế strategy (strategy pattern).
- Trước hết, tôi sẽ giải thích cho những ai chưa quen với mẫu thiết kế Strategy. Đây là mẫu thiết kế giúp bạn trừu tượng hoá (abstract) những hành vi (behavior, method, function) của đối tượng với nhiều cách bằng cách đưa những cài đặt (implement) đó vào những lớp (class) khác nhau. Giả sử tôi nhận được yêu cầu thiết kế một lớp với một behavior nào đó, và tôi nghĩ ra được rất nhiều cách để làm cài đặt behavior này. Nếu không biết trước mẫu thiết kế strategy, tôi có thể implement một lớp đơn giản với nhiều method với những tên khác nhau. Khi nào muốn sử dụng cách nào thì tôi gọi method đấy. Cách này cũng giải quyết được bài toán nhưng tồn tại bên trong nó nhiều risk tiềm ẩn và theo tôi nó không có tính hướng đối tượng. Nếu làm theo cách đó chẳng khác thời mình học Pascal là mấy, lập trình thủ tục. Mục tiêu của design pattern là giúp chúng ta giải quyết các bài toán kiểu như vậy và trường hợp này chúng ta có thể sử dụng mẫu thiết kế Strategy cho chương trình của mình. Trước lúc tôi nấu lại bài viết của tác giả, rất nhiều lần tôi đã vô tình sử dụng mẫu thiết kế này nhưng không hề biết đó là mẫu Strategy và tôi nghĩ cũng có một số người giống như tôi. Trong yêu cầu trên, thay vì implement cách tạo ra những method khác nhau với những tên khác nhau, tôi sẽ đưa những xử lý trong các method ấy ra nhiều lớp độc lập. Những lớp này sẽ cùng implement một interface và chương trình của tôi sẽ giữ một instance thuộc về kiểu Interface này. Nói cách khác, một xử lý phức tạp không được chia nhỏ theo nhiều hàm mà chia nhỏ ra nhiều lớp, hướng tiếp cận này sẽ giúp các bạn tăng tính loose coupling giữa những đối tượng và chương trình của bạn sẽ rất dễ sửa đổi về sau. Những tài liệu design pattern thường dùng 1 ví dụ kinh điển về các thuật toán sort để giải thích mẫu thiết kế Strategy. Các bạn có thể tham khảo bài gốc của tác giả hoặc trên trang wiki cũng tốt.Tuy nhiên tôi sẽ sử dụng một ví dụ khác bình dân hơn và vui hơn để những bạn muốn tìm hiểu mẫu thiết kế Strategy có thể tiếp nhận một cách nhẹ nhàng.
- Giả sử, à không, tất nhiên bây giờ tôi đã có một cô bạn gái , và cuối tuần phải dẫn đi đâu đó chơi. Theo tôi, có nhiều cách đi chơi cuối tuần (strategy) như đi ăn tối, đi xem phim, đi café, đi karaoke, nấu ăn, … Tất cả những cách đấy đều cùng mục đích là giải trí vào ngày cuối tuần và sau khi thực hiện những cách ấy tôi và bạn gái của tôi đều nhận được một kết quả là cả 2 đều cảm thấy hài lòng, tinh thần thoải mái để bắt đầu một tuần làm việc mới
. Tuy quá trình thực hiện những cách ấy có khác nhau, thời gian địa điểm khác nhau nhưng đều giải quyết được yêu cầu của tôi. Như vậy tôi vẽ bản kế hoạch như sau:
Hình 6: Bản kế hoạch ăn chơi theo Strategy Pattern
- Và đây là lịch đi chơi tuần tới hê hê, xin mẹ được có 100 ngàn thì đi coi film chứ sao giờ.
public class NguyenThoai : IHuman {
private int _money = 0;
private Vehicle _vehicle = null;
private bool _isHappy = false;
private ICachDiChoi _cachDiChoi;
public NguyenThoai(ICachDiChoi cachDiChoi) {
_cachDiChoi = cachDiChoi;
}
public void DanBanGaiDiChoi() {
_cachDiChoi.DiChoi();
}
// implement some public properties...
}
public interface ICachDiChoi {
void DiChoi();
}
public class DiXemPhim : ICachDiChoi {
public void DiChoi() {
// _isHappy = true;
}
}
public class DiAnToi : ICachDiChoi {
public void DiChoi() {
// _isHappy = true;
}
}
public class DiUongCafe : ICachDiChoi {
public void DiChoi() {
// _isHappy = true;
}
}
public class ThuBayTuanToi {
public void main() {
NguyenThoai _thoai = new NguyenThoai(new DiXemPhim());
_thoai.Money = 100.000 VND;
_thoai.Vehicle = new Vehicle("Xe Bus");
_thoai.DanBanGaiDiChoi();
}
}
Hình 7: Chương trình ăn chơi
- Mẫu thiết kế Strategy pattern rất hữu ích khi bạn muốn trừu tượng hoá những thuật toán, những xử lý phức tạp và những gì có thể bạn sẽ thay đổi sau này. Tuy thực hiện việc dẫn bạn gái đi chơi là không quá phức tạp nhưng tôi hi vọng ví dụ trên có thể giúp bạn hiểu được Strategy pattern là gì, nó giải quyết vấn đề gì và khi nào ta nên sử dụng Strategy Pattern. Nếu như bạn vẫn chưa hiểu rõ và còn mơ hồ, bạn có thể phản hồi bằng cách comment trược tiếp vào entry này. Ngoài ra, nếu có quan tâm đến những Design Pattern khác, bạn sẽ thấy rằng mẫu thiết kế Bridge sẽ có sơ đồ lớp (Class Diagram) rất giống với mẫu thiết kế Strategy nhưng nó sẽ giải quyết một vấn đề khác tương tự. Hi vọng tôi có cơ hội để nấu nướng về mẫu thiết kế Bridge này. Quay lại ví dụ của chúng ta, như thiết kế lớp ở trên, chúng ta sẽ tìm hiểu cách sử dụng Spring.Net để chuyển đổi các strategy bằng file config; để có thể dễ dàng thay đổi thuật toán trong ứng dụng mà chẳng cần phải build lại chương trình. Xem phần tiếp theo.
(Các bạn đang xem phần 3)