Factory Pattern bao gồm 2 patterns là Factory Method và Abstract Factory. Ở phần 1 của Factory Pattern này mình sẽ giới thiệu và phân tích về Factory Method trong Factory Pattern.
Bài toán cần giải quyết
Lại nói về vấn database để lấy ví dụ. Giả sử, ở hiện tại, để tạo một database connection object các dev của chúng ta đang làm như sau →
1 |
OracleConnection *connection = new OracleConnection(); |
Sau khi tạo connection chúng ta có thể connect đến Oracle databases. Nhưng nếu bây giờ khách hàng muốn đổi sang dùng Microsoft’s SQL Server databases thì sao ?
OK, chúng sẽ phải tạo class SqlServerConnection (cùng interface với OracleConnection) và sửa lại đoạn code tạo connection sang dùng class mới →
1 |
SqlServerConnection *connection = new SqlServerConnection(); |
Vào một ngày đẹp trời, à không, xấu trời mới đúng, khách hàng lại muốn đổi sang MySQL databases thì như nào ? Lại phải tạo ra class mới MySqlConnection (cùng interface với OracleConnection, SqlServerConnection) và lại đổi sang dùng nó để tạo connection thôi →
1 |
MySqlConnection *connection = new MySqlConnection(); |
Áp dụng Factory Method
Bây giờ chúng ta đã có ba loại database connection khác nhau là: Oracle, SQL Server và MySQL. Vì vậy, chúng ta nên điều chỉnh lại code để tạo connection một cách linh hoạt hơn để đối phó với sự thay đổi có thể xảy ra trong tương lai. Chúng ta sẽ tạo instance của connection dựa vào giá trị trong một biến là connectionType (giá trị của connectionType được set vào trước khi tạo connection).
*** khai báo enum cho connection types
1 2 3 4 5 6 7 8 |
// define enum type for connection types enum eConnectionType { CONNECTION_TYPE_ORACLE = 0, CONNECTION_TYPE_SQLSERVER, CONNECTION_TYPE_MYSQL, CONNECTION_TYPE_DEFAULT }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
class Connection { // define interfaces virtual string description() = 0; virtual void setDbName(string dbName) = 0; virtual void setUsername(string username) = 0; virtual void setPassword(string password) = 0; virtual bool initialize() = 0; . . . }; class OracleConnection : public Connection { // implemement Connection's interfaces string description() { return "OracleConnection"; }; . . . }; class SqlServerConnection : public Connection { // implemement Connection's interfaces string description() { return "SqlServerConnection"; }; . . . }; class MySqlConnection : public Connection { // implemement Connection's interfaces string description() { return "MySqlConnection"; }; . . . }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// connectionType is a variable of eConnectionType type // value of connectionType is set base on some business logical ... Connection *connection = nullptr; switch (connectionType) { case CONNECTION_TYPE_ORACLE: connection = new OracleConnection(); break; case CONNECTION_TYPE_SQLSERVER: connection = new SqlServerConnection(); break; case CONNECTION_TYPE_MYSQL: connection = new MySqlConnection(); break; default: connection = new OracleConnection(); // default is Oracle break; } |
Tạm ổn. Tuy nhiên trong chương trình có thể rất nhiều chỗ cần tạo database connection, nếu có khoảng 200 chỗ cần tạo database connection chúng ta sẽ có khoảng 200 đoạn code switch case như ở trên ==> ngu học. Đã đến lúc đặt đoạn code tạo connection vào một method của một class để tránh trùng lặp code (nguyên tắc DRY – Don’t Repeat Yourseft) và dễ maintain →
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class DbConnectionFactory { public: static Connection* createConnection(eConnectionType connectionType); }; Connection* DbConnectionFactory::createConnection(eConnectionType connectionType) { Connection *connection = nullptr; switch (connectionType) { case CONNECTION_TYPE_ORACLE: connection = new OracleConnection(); break; case CONNECTION_TYPE_SQLSERVER: connection = new SqlServerConnection(); break; case CONNECTION_TYPE_MYSQL: connection = new MySqlConnection(); break; default: connection = new OracleConnection(); // default is Oracle break; } return connection; } |
Hàm DbConnectionFactory::createConnection chính là cái gọi là Factory Method dùng để tạo ra connection instance dựa trên tham số truyền vào. Bây giờ chúng ta có thể tạo instance của connection theo cách sau →
1 |
Connection *connection = DbConnectionFactory::createConnection(CONNECTION_TYPE_SQLSERVER); |
Ưu điểm của việc sử dụng Factory Method
- Tách biệt công việc tạo object / instance ra khỏi xử lý của client code (client code là phần code sử dụng object / instance để thực hiện hoàn thành công việc của nó). Client code sẽ không cần phải quan tâm instance được tạo ra như thế nào, nó là object của class nào, nhờ đó giảm thiểu dependency của client code (Nguyên tắc: Loose Coupling).
- Dễ maintain: nếu muốn thêm/bớt các connection class, sửa tên connection class thì chỉ cần sửa một method mà không làm ảnh hưởng đến các phần code khác. (vd: trong trường hợp connection instance ở trên thì chỉ cần sửa hàm DbConnectionFactory::createConnection)
Note
Trong bài này mình đưa ra cách tiếp cận dựa theo cuốn sách Design Patterns for Dummies nên sẽ hơi khác một chút so với cách tiếp cận thông thường theo cuốn Design Pattern – Elements Reusable Object-Oriented Software. Vì theo kinh nghiệm của mình thì cách tiếp cận này dễ hiểu, hợp lý hơn, nếu bạn nào muốn tìm hiểu thêm theo cách tiếp cận khác thì có thể tham khảo cuốn sách mình vừa mention ở trên.
Ở bài tiếp theo mình sẽ tiếp tục giới thiệu tới mọi người pattern thứ 2 trong Factory Pattern là Abstract Factory.— Phạm Minh Tuấn (Shun) —