Factory Pattern là một Design Pattern là tiền đề để rất nhiều các Design Pattern khác sử dụng (Factory Method Pattern, Abstract Factory Pattern, …). Bài viết dưới đây sẽ giới thiệu về Factory Pattern và cách giải quyết 1 số vấn đề của nó trong Java.
Class Diagram
Class Diagram

Ưu điểm
Factory pattern đưa ra 1 ý tưởng mới cho việc khởi tạo các instance phù hợp với mỗi request từ phía Client. Sử dụng Factory pattern sẽ có những ưu điểm sau:
  • Tạo ra 1 cách mới trong việc khởi tạo cá Object thông qua 1 interface chung.
  • Khởi tạo các Objects mà che giấu đi xử lí logic của việc khởi tạo đấy.
  • Giảm sự phụ thuộc giữa các module, các logic với các class cụ thể, mà chỉ phụ thuộc vào interface hoặc abstract class.
Triển khai
  • Dựa vào class diagram thì ta cần 1 interface Product. Mục đích là để tạo ra 1 interface hoặc abstract class cho các class mà mình sẽ sử dụng.
public interface Product {  
    String getName();
}
  • Chúng ta tạo ra 2 class implement interface Product trên:
public class ProductOne implements Product {  
    public String getName() {
        return "Product One";
    }
}
public class ProductTwo implements Product {  
    public String getName() {
        return "Product Two";
    }
}
  • Tạo class Factory dựa vào 1 dấu hiệu (hoặc logic nào đó) để tạo ra các instance của các class ProductOne, ProductTwo. Ở đây mình sẽ sử dụng productId để phân loại các Product. (class Factory hoàn toàn có thể implement theo Singleton pattern)
public class Factory {  
    private static Factory instance;

    private Factory() {        
    }

    public static Factory getInstance() {
        if (instance == null) {
            instance = new Factory();
        }
        return instance;
    }

    public Product createProduct(String productId) {
        if(productId == null) {
            return null;
        }
        if(productId.equals("01")) {
            return new ProductOne();
        }
        if(productId.equals("02")) {
            return new ProductTwo();
        }
        return null;
    }
}
Thông thường ở Client, khi sử dụng thì chúng ta sẽ thường làm như sau:
public class Client {  
    public static class main(String[] args) {
        Product one = new ProductOne();
        Product two = new ProductTwo();

        // Xử lí logic
    }
}
Client khi sử dụng Factory Pattern sẽ như sau:
public class Client {  
    public static void main(String[] args) {
        Product one = Factory.getInstance().createProduct("01");
        Product two = Factory.getInstance().createProduct("02");

        // Xử lí logic
        System.out.println("Product name: " + one.getName());
        System.out.println("Product name: " + two.getName());
    }
}
Tối ưu bài toán
Lưu ý: Bắt đầu từ phần này, đoạn code của Singleton sẽ không viết lại nữa mà chỉ viết … Hãy tham khảo lại phần trên để sử dụng Singleton
Cách tiếp cận 1: Cách tiếp cận truyền thống (Phần code của Singleton xin tham khảo ở trên)
public class Factory {  
    ....
    public Product createProduct(String productId) {
        if(productId == null) {
            return null;
        }
        if(productId.equals("01")) {
            return new ProductOne();
        }
        if(productId.equals("02")) {
            return new ProductTwo();
        }
        return null;
    }
}
Ưu điểm
  • Dễ dàng triển khai code.
  • Client không phụ thuộc vào subclass của Product.
Nhược điểm
  • Tuy Client không phụ thuộc vào subclass của Product nhưng class Factorylại bị phụ thuộc.
  • Chính vì bị phụ thuộc như trên nên khi thay đổi (hoặc thêm mới) các Productsẽ bắt buộc phải chỉnh sửa lại clas Factory.
Cách tiếp cận 2: Cách tiếp cận với việc đăng kí vào Factory và sử dụng Reflection
Ta sẽ tìm hiểu 1 cách tiếp cận mới mà không cần sử dụng quá nhiều từ khóa newvà các đoạn if-else
Class Factory sẽ thêm method registerProduct và sử dụng và sử dụng 1 Map để khởi tạo các Object như sau:
public class Factory {  
   ...
    private HashMap<String, Class> productMapping = new HashMap<String, Class>();

    public void registerProduct(String productID, Class productClass) {
        productMapping.put(productID, productClass);
    }

    public Product createProduct(String productID) {
        try {
            Class productClass = productMapping.get(productID);
            Constructor productConstructor = productClass.getConstructor();
            return (Product) productConstructor.newInstance();
        } catch (Exception e) {
            return null;
        }
    }
}

Class Client:

public class Client {

    static {
        Factory.getInstance().registerProduct("01", ProductOne.class);
        Factory.getInstance().registerProduct("02", ProductTwo.class);
    }

    public static void main(String[] args) {
        Product one = Factory.getInstance().createProduct("01");
        Product two = Factory.getInstance().createProduct("02");

        // Xử lí logic
        System.out.println("Product name: " + one.getName());
        System.out.println("Poduct name: " + two.getName());
    }
}
Client cần phải đăng kí các Product vào Factory trước khi sử dụng. Đoạn code trong phần static {} ở đầu class Client chính là để thực hiện việc đó.
Ưu điểm – Giảm sự phụ thuộc giữa Factory và subclass của Product. – Loại bỏ logic xử lí phức tạp ở các đoạn if-else. – Hạn chế sử dụng từ khóa new.
Nhược điểm – Client bắt buộc phải đăng kí các Product trước khi sử dụng Factory. – Chỉ hỗ trợ cho các ngôn ngữ có Reflection. – Khó để triển khai code. -Reflection khiến chương trình chạy chậm hơn.
Cách tiếp cận 3: Tự đăng kí vào Factory
Cách thứ 3 sẽ xử lí 1 số vấn đề nhược điểm của cách thứ 2.
Ở interface Product ta sẽ thêm method CreateProduct() để tạo ra instance tương ứng các subclass của Product.
public interface Product {  
    String getName();
    Product createProduct();
}
Sau khi triển khai method createProduct(), ta tiếp tục viết method static registerProduct() để làm nhiệm vụ đăng kí Product đó vào Factory:
public static void registerProduct(String productID) {  
        Factory.getInstance().registerProduct(productID, new ProductOne());
    }
}

Class ProductTwo tương tự

Class Factory cũng sử dụng Map như cách tiếp cận thứ 2, nhưng thay vì là Map<string,> thì ở cách thứ 3 sẽ sử dụng Map<string, product=””>. Ngoài ra sẽ sửa method registerProduct như sau:
public void registerProduct(String productID, Product product) { productMapping.put(productID, product); }
Class Client phần register Product sẽ sửa như sau:
static {  
        ProductOne.registerProduct("01");
        ProductTwo.registerProduct("02");
    }
Ưu điểm
  • Có tất cả các ưu điểm của cách tiếp cận thứ 2.
Nhược điểm
  • Client vẫn phải đăng kí các Product vào Factory trước khi sử dụng.
  • Chỉ hỗ trợ cho các ngôn ngữ có Reflection.
  • Khó để triển khai code.
  • Reflection khiến chương trình chạy chậm hơn.
Tuy vẫn còn một số nhược điểm, nhưng do tư tưởng của Factory Pattern rất hay nên nó thường được sử dụng trong các Design Pattern khác như Factory Method Pattern, Abstract Factory Pattern, … hoặc là được sử dụng trong các bài toán cụ thể, mà những cách tiếp cận trên phù hợp với bài toán đó.
Link tham khảo: http://www.codeproject.com/Articles/37547/Exploring-Factory-Pattern
Nguồn ảnh: http://blog.salaria.net
Dương Thanh Hải
Tin liên quan: