Search This Blog

Sunday, June 12, 2016

Design Patterns trong các dự án thực tế - Factory Pattern

Lời nói đầu

Trước khi tìm hiểu về Factory Pattern, hãy đọc lại bài viết Design patterns là gì nếu bạn vẫn chưa biết design patterns là gì. Ngoài ra để giúp bạn dễ dàng nắm bắt nội dung bài viết, hãy chắc là bạn đã biết về Lập trình hướng đối tượng (OOP) và các tính chất, khái niệm của nó như bao đóng (encapsulation), trừu tượng (abstract), kế thừa (inheritance), đa hình (polymorphism), đối tượng (object), lớp (class), giao diện (interface)...
***
Factory Pattern là gì?

Factory Pattern là một trong các pattern thông dụng nhất trong thiết kế phần mềm, thuộc nhóm creational pattern tức là thông qua pattern này để tạo ra đối tượng (object).

Factory pattern định nghĩa một giao diện (interface) hay một lớp cha (parent class) để tạo đối tượng (object) với các class con kế thừa từ interface hay class cha đó. Các class con này có thể tự quyết định cách thức hoạt động của mình. Ngoài ra có một Factory để tạo ra các class con này mà không cho bên ngoài biết cách thức tạo ra chúng.

Để dễ hiểu, chúng ta hãy thử liên tưởng đến việc sản xuất đồ uống của một nhà máy (factory) và chúng ta là những người sử dụng các sản phẩm (nước ngọt, bia, rượu...) của nhà máy đó. Chúng ta có thể đoán biết được việc sản xuất nước ngọt, bia... có thể cần nguyên liệu gì (đầu vào) nhưng chúng ta hoàn toàn không biết được làm thế nào mà nhà máy có thể sản xuất ra được thành phẩm (nước ngọt, bia, rượu). Đó chính là cách mà của Factory pattern hoạt động.

Áp dụng:

Kiên lấy lại ví dụ từ bài Design Patterns là gìHãy viết một chức năng kết nối database, mà chức năng này phải đảm bảo hoạt động tốt và dễ cấu hình cho các hệ quản trị cơ sở dữ liệu (DBMS) khác nhau như MySQL, MSSQL, Oracle. 

Như các bạn thấy, yêu cầu này muốn chúng ta tạo kết nối đến database dựa vào tham số đầu vào là MySQL, MSSQL hay Oracle và kết nối được tạo ra phải là một trong ba hệ quản trị cơ sở dữ liệu (DBMS) này. Với yêu cầu này, việc áp dụng Factory pattern sẽ được cân nhắc.


Để hiểu vì sao chúng ta lại áp dụng Factory Pattern trong ví dụ này, chúng ta thay đổi lại yêu cầu ban đầu: Hãy viết một chức năng kết nối database, mà chức năng này phải đảm bảo hoạt động tốt và dễ cấu hình cho MSSQL. Tức là yêu cầu ban đầu chỉ mong muốn chúng ta kết nối và hoạt động tốt với MSSQL - hệ quản trị cơ sở dữ liệu (DBMS) của Microsoft.


Yêu cầu khá dễ dàng đúng không? Trong ví dụ này Kiên sẽ sử dụng Entity Framework để tạo mapping đến cơ sở dữ liệu (database). Chi tiết về Entity Framework sẽ được giới thiệu trong các loạt bài sau.


Chúng ta cùng bắt đầu!


BƯỚC ĐẦU TIÊN: Sử dụng Entity Framework để tạo mapping từ database


Hãy tham khảo bài viết NÀY để tạo ra mapping cũng như connection string kết nối đến MSSQL thông qua mô hình Entity Framework - Database First.

BƯỚC TIẾP THEO: Tạo chuỗi kết nối kết nối tương ứng với MSSQL, MySQL, Oracle...

- Chúng ta đã hoàn thành yêu cầu đơn giản là tạo kết nối đến MSSQL. Hãy xem lại cách mà ConnectionString của chúng ta được tạo ra trong BÀI VIẾT NÀY

 public override string GetConnectionString()  
     {  
       SqlConnectionStringBuilder sqlBuilder = new SqlConnectionStringBuilder();  
       sqlBuilder.DataSource = @"VNWKTTV093\SQLEXPRESS";  
       sqlBuilder.InitialCatalog = "FactoryPattern";  
       sqlBuilder.MultipleActiveResultSets = true;  
       sqlBuilder.IntegratedSecurity = true;  
       sqlBuilder.ApplicationName = "EntityFramework";  
       EntityConnectionStringBuilder efBuilder = new EntityConnectionStringBuilder();  
       efBuilder.Metadata = "res://*/FactoryModel.csdl|res://*/FactoryModel.ssdl|res://*/FactoryModel.msl";  
       efBuilder.Provider = "System.Data.SqlClient";  
       efBuilder.ProviderConnectionString = sqlBuilder.ConnectionString;  
       return efBuilder.ConnectionString;  
     }  

- Nhưng yêu cầu ban đầu của chúng ta là phải kết nối được với 3 DBMS, vậy nếu chúng ta phải kết nối đến MySQL hay Oracle thì dễ thấy cách làm đơn giản nhất là chúng ta phải vào method GetConnectionString này và thay đổi các thông số như DataSource, Metadata, Provider... tương ứng với DBMS mà chúng ta cần kết nối...Quá trình này sẽ lặp đi lặp lại nếu chúng ta có nhiều dự án với yêu cầu các DBMS khác nhau. Điều này nghe có vẻ đơn giản nhưng nó sẽ tạo cho chúng ta thói quen không tốt khi phải sửa đi sửa lại các dòng code đã hoàn chỉnh của mình.

- Giải pháp khác là chúng ta thiết lập một DbType kiểu Enum bao gồm các loại DBMS mà chúng ta mong muốn kết nối, sau đó truyền biến này vào method GetConnectionString. Bên trong method sẽ xử lí kết nối thông qua switch..case hay mệnh đề if. Giải pháp này giải quyết được vấn đề bên trên, giúp chúng ta tránh phải thay đổi code theo thời gian. Nhưng nó cũng tồn tại một vấn đề là có quá nhiều việc phải làm bên trong class Connection này. Có quá nhiều lệnh switch..case hay if sẽ khiến cho class của chúng ta trở nên rối rắm, phức tạp. Giả sử class của chúng ta lớn, có nhiều xử lí...thì khi có bổ sung hay cập nhật sẽ khiến cho chúng ta rơi vào ma trận code, dễ xảy ra tình trạng râu ông này cắm cằm bà kia.

- Factory Pattern sẽ giải quyết được các vấn đề bên trên. Hãy xem sơ đồ UML bên dưới để hiểu cách mà Factory Pattern được áp dụng:


- Chúng ta hãy thay đổi class Connection lại một chút. Đầu tiên điều chỉnh class Connection là dạng abstract class, sau đó chúng ta cũng khai báo lại method GetConnectionString cũng là abstract method như hình 19. Điều này nhằm chỉ ra class Connection giờ đây là một class cha và không còn xử lí chung nữa, và các class con kế thừa phải tự thực thi lại theo cách riêng của chính nó.
(hình 19)
- Ta cũng tạo 3 class con là MSSqlConnection, MySQLConnection, OracleConnection kế thừa từ abstract class Connection này. Đồng thời override lại method GetConnectionString để xử lí. Khá rõ ràng và dễ dàng nếu chúng ta có cập nhật, bổ sung về sau.

MSSqlConnection.cs: chúng ta copy lại đoạn code lúc đầu để tạo ra ConnectionString cho MSSQL. Chúng ta cũng làm tương tự cho hai class còn lại và thay đổi các thông số cần thiết.
(hình 20)
MySqlConnection.cs
(hình 21)
OracleConnection.cs
(hình 22)

- Điều quan trọng tiếp theo là chúng ta phải có một Factory để tạo ra các Connection tương ứng. Ngoài ra để xác định loại Connection chúng ta cũng tạo một kiểu enum có tên DbType như hình 23.

- Thêm mới một class gọi là ConnectionFactory và một static method CreateConnection có tham số đầu vào là kiểu enum DbType, giá trị trả về là đối tượng thuộc class Connection. Bên trong method, chúng ta dựa vào dbType để trả về chính xác đối tượng thuộc class kết nối nào. Nếu dbType yêu cầu MSSQL thì chúng ta trả về đối tượng MSSqlConnection và tương ứng cho các dbType khác. Minh họa như hình 23.

(hình 23)
- Chúng ta cũng cập nhật đoạn code khởi tạo Connection trong sự kiện btnSearch_Click như hình 24 băng cách gọi hàm CreateConnection của ConnectionFactory và truyền tham số dbType mà ta mong muốn.
(hình 24)

- Run Debugging (F5) để đảm bảo mọi thứ vẫn hoạt động tốt. Các bạn có thể tạo các kết nối đến MySQL để kiểm tra thêm.


Như vậy là chúng ta đã xây dựng xong một cơ chế kết nối multi DBMS áp dụng Factory Pattern. Ví dụ trên cũng khá đơn giản nhằm mục đích giúp các bạn dễ hiểu hơn mà Kiên sẽ không đi sâu vào những vấn đề khác.

Ngoài ra để các bạn làm thử, Kiên có một ví dụ mở rộng khác là: Không throw exception mà hãy handle được các lỗi có thể xảy ra khi kết nối, tương tác database (CRUD) của 3 DBMS nói riêng và các exception nói chung và trả ra các mã lỗi tương ứng. Các bạn sẽ áp dụng Factory Pattern vào để xử lí yêu cầu này thế nào? Câu trả lời sẽ đến trong các bài tiếp theo.

Nếu có gì trao đổi hay thắc mắc, các bạn hãy để lại comment bên dưới dùm mình.

1 comment: