Factory Pattern: 'Nhà máy' sản xuất đối tượng xịn xò trong Java OOP
Java – OOP

Factory Pattern: 'Nhà máy' sản xuất đối tượng xịn xò trong Java OOP

Author

Admin System

@root

Ngày xuất bản

23 Mar, 2026

Lượt xem

1 Lượt

Chào anh em developer tương lai, hôm nay anh Creyt sẽ cùng các em "đập hộp" một "thằng cha" design pattern cực kỳ quyền năng và được sử dụng rộng rãi trong giới lập trình, đó là Factory Pattern. Nghe tên có vẻ "công nghiệp" đúng không? Chuẩn rồi đấy, nó chính là "nhà máy sản xuất" ra các đối tượng cho ứng dụng của chúng ta.

1. Factory Pattern là gì và nó sinh ra để làm gì?

Tưởng tượng mà xem, anh em GenZ chúng ta hay thích "ăn liền" đúng không? Order đồ ăn online, chỉ cần chọn món, bấm nút là có người giao tận nơi, không cần biết món đó được nấu ở bếp nào, bởi đầu bếp nào, dùng nguyên liệu gì. Cái "người giao hàng" hoặc "hệ thống order" đó, chính là một dạng của Factory Pattern đấy.

Trong lập trình, đặc biệt là với Java OOP, đôi khi chúng ta cần tạo ra các đối tượng (object) mà loại đối tượng cụ thể lại phụ thuộc vào một điều kiện nào đó lúc chạy chương trình. Ví dụ, anh em có một ứng dụng quản lý xe cộ, có thể là Car, Motorcycle, Truck. Tùy vào yêu cầu của người dùng mà chúng ta cần tạo ra loại xe phù hợp.

Nếu không có Factory Pattern, anh em sẽ phải viết code kiểu như này:

// Trong một class nào đó
Vehicle vehicle;
String vehicleType = getUserInput(); // Giả sử người dùng nhập 'car' hoặc 'motorcycle'

if (vehicleType.equals("car")) {
    vehicle = new Car();
} else if (vehicleType.equals("motorcycle")) {
    vehicle = new Motorcycle();
} else if (vehicleType.equals("truck")) {
    vehicle = new Truck();
} else {
    throw new IllegalArgumentException("Loại xe không hợp lệ!");
}
// ... dùng vehicle

Nhìn vào đoạn code trên, anh em thấy gì không? Một "ổ" if-else dài ngoằng, mỗi khi muốn thêm một loại xe mới (ví dụ Bicycle), anh em lại phải mò vào tất cả những chỗ có đoạn code tạo đối tượng này để sửa. Đây chính là cái mà dân chuyên nghiệp gọi là "tight coupling" (kết nối chặt chẽ) và vi phạm nguyên tắc "Open/Closed Principle" (mở rộng thì mở, sửa đổi thì đóng). Code sẽ nhanh chóng biến thành "mì Ý" (spaghetti code) nếu anh em làm lớn.

Factory Pattern ra đời để giải quyết bài toán này. Nó cung cấp một phương thức để tạo ra các đối tượng mà không cần phải chỉ rõ lớp cụ thể nào sẽ được tạo ra. Thay vì tự tay new một đối tượng, anh em sẽ nhờ "nhà máy" (Factory) làm việc đó. "Nhà máy" này sẽ biết cách tạo ra đối tượng phù hợp dựa trên yêu cầu của anh em.

Nói cách khác, Factory Pattern giúp:

  • Giấu đi sự phức tạp khi tạo đối tượng: Anh em chỉ cần nói "cho tôi một cái xe hơi", không cần biết xe hơi đó được lắp ráp từ những bộ phận nào, bởi ai.
  • Giảm sự phụ thuộc (decoupling): Class sử dụng đối tượng không còn phụ thuộc trực tiếp vào các class cụ thể của đối tượng đó nữa. Nó chỉ làm việc với một interface hoặc abstract class chung.
  • Dễ dàng mở rộng: Khi muốn thêm một loại xe mới, anh em chỉ cần tạo class mới cho xe đó và chỉnh sửa duy nhất trong Factory. Các đoạn code sử dụng Factory sẽ không cần thay đổi.

2. Code Ví Dụ Minh Hoạ (Java)

Hãy cùng xây dựng một "nhà máy" sản xuất cà phê nhé. Anh em GenZ ai mà không mê cà phê đúng không?

Đầu tiên, chúng ta cần một interface cho sản phẩm của mình – ở đây là Coffee.

// 1. Interface cho sản phẩm (Coffee)
interface Coffee {
    void brew();
    void serve();
}

Tiếp theo, là các loại cà phê cụ thể (sản phẩm cụ thể):

// 2. Các lớp sản phẩm cụ thể
class Espresso implements Coffee {
    @Override
    public void brew() {
        System.out.println("Pha Espresso: Nước nóng áp suất cao qua cà phê xay mịn.");
    }

    @Override
    public void serve() {
        System.out.println("Phục vụ một shot Espresso đậm đà.");
    }
}

class Latte implements Coffee {
    @Override
    public void brew() {
        System.out.println("Pha Latte: Espresso với sữa nóng và một lớp bọt sữa.");
    }

    @Override
    public void serve() {
        System.out.println("Phục vụ một ly Latte art đẹp mắt.");
    }
}

class Cappuccino implements Coffee {
    @Override
    public void brew() {
        System.out.println("Pha Cappuccino: Espresso, sữa nóng và bọt sữa dày.");
    }

    @Override
    public void serve() {
        System.out.println("Phục vụ một ly Cappuccino truyền thống.");
    }
}

Giờ là lúc "nhà máy" cà phê của chúng ta xuất hiện – CoffeeFactory:

// 3. Lớp Factory
class CoffeeFactory {
    public Coffee createCoffee(String type) {
        if (type == null || type.isEmpty()) {
            return null;
        }
        switch (type.toLowerCase()) {
            case "espresso":
                return new Espresso();
            case "latte":
                return new Latte();
            case "cappuccino":
                return new Cappuccino();
            default:
                throw new IllegalArgumentException("Loại cà phê không hợp lệ: " + type);
        }
    }
}

Và đây là cách "khách hàng" (client code) sử dụng nhà máy này:

// 4. Client code sử dụng Factory
public class CoffeeShop {
    public static void main(String[] args) {
        CoffeeFactory factory = new CoffeeFactory();

        System.out.println("\n--- Khách hàng muốn Espresso ---");
        Coffee myEspresso = factory.createCoffee("espresso");
        if (myEspresso != null) {
            myEspresso.brew();
            myEspresso.serve();
        }

        System.out.println("\n--- Khách hàng muốn Latte ---");
        Coffee myLatte = factory.createCoffee("latte");
        if (myLatte != null) {
            myLatte.brew();
            myLatte.serve();
        }

        System.out.println("\n--- Khách hàng muốn Cappuccino ---");
        Coffee myCappuccino = factory.createCoffee("cappuccino");
        if (myCappuccino != null) {
            myCappuccino.brew();
            myCappuccino.serve();
        }

        // Thử với loại không tồn tại
        try {
            System.out.println("\n--- Khách hàng muốn Americano (chưa có) ---");
            Coffee americano = factory.createCoffee("americano");
        } catch (IllegalArgumentException e) {
            System.out.println("Lỗi: " + e.getMessage());
        }
    }
}

Output của chương trình:

--- Khách hàng muốn Espresso ---
Pha Espresso: Nước nóng áp suất cao qua cà phê xay mịn.
Phục vụ một shot Espresso đậm đà.

--- Khách hàng muốn Latte ---
Pha Latte: Espresso với sữa nóng và một lớp bọt sữa.
Phục vụ một ly Latte art đẹp mắt.

--- Khách hàng muốn Cappuccino ---
Pha Cappuccino: Espresso, sữa nóng và bọt sữa dày.
Phục vụ một ly Cappuccino truyền thống.

--- Khách hàng muốn Americano (chưa có) ---
Lỗi: Loại cà phê không hợp lệ: americano

Thấy chưa anh em? Giờ đây, class CoffeeShop (client) chỉ cần biết đến CoffeeFactory và interface Coffee, nó không cần biết chi tiết Espresso, Latte hay Cappuccino được tạo ra như thế nào. Nếu sau này anh em muốn thêm Americano, chỉ cần tạo class Americano và thêm một case vào CoffeeFactory là xong, CoffeeShop không cần động chạm gì cả. Quá là "ổn áp"!

3. Mẹo hay và Best Practices từ anh Creyt

  • Khi nào nên dùng?

    • Khi class của anh em không biết trước loại đối tượng cụ thể nào sẽ cần tạo ra. Quyết định tạo đối tượng nào phụ thuộc vào dữ liệu đầu vào, cấu hình, hoặc môi trường runtime.
    • Khi anh em muốn tập trung logic tạo đối tượng vào một nơi duy nhất. Điều này giúp dễ dàng quản lý, sửa lỗi và mở rộng.
    • Khi anh em muốn tách biệt code tạo đối tượng khỏi code sử dụng đối tượng (decoupling).
    • Khi anh em có nhiều if-else hoặc switch để tạo các đối tượng con từ một interface/abstract class chung.
  • Khi nào không nên "làm màu" dùng Factory?

    • Nếu anh em chỉ có một loại đối tượng để tạo, hoặc việc tạo đối tượng rất đơn giản và không có logic phức tạp, thì dùng new trực tiếp là đủ. Đừng cố "nhà máy hóa" mọi thứ, đôi khi đơn giản là đẹp nhất.
  • Ghi nhớ: Hãy coi Factory như một "công nhân chuyên trách" việc sản xuất. Anh em chỉ cần đưa yêu cầu, nó sẽ giao đúng sản phẩm cho anh em, không cần anh em phải tự tay lắp ráp.

  • Lợi ích "thầm kín": Factory Pattern cực kỳ hữu ích trong việc viết Unit Test. Anh em có thể dễ dàng mock (giả lập) hoặc stub (cài đặt tạm thời) Factory để kiểm soát việc tạo đối tượng trong các bài test của mình.

4. Ứng dụng thực tế: Factory Pattern "phủ sóng" ở đâu?

Factory Pattern không chỉ là lý thuyết suông đâu anh em, nó xuất hiện ở khắp mọi nơi trong các framework và ứng dụng lớn:

  • Java Database Connectivity (JDBC): Khi anh em dùng DriverManager.getConnection(url, user, password);, anh em không hề biết driver cụ thể nào (ví dụ: MySQL, PostgreSQL) được sử dụng để tạo kết nối. DriverManager chính là một Factory, nó tự động tìm và tạo ra đối tượng Connection phù hợp với URL của anh em.
  • Spring Framework: Đây là "ông hoàng" của Dependency Injection, và Factory Pattern là một phần cốt lõi của nó. BeanFactory hoặc ApplicationContext của Spring hoạt động như một Factory khổng lồ, chịu trách nhiệm tạo và quản lý các bean (đối tượng) trong ứng dụng của anh em.
  • Graphical User Interface (GUI) Toolkits: Các framework như Swing, JavaFX thường sử dụng Factory để tạo ra các thành phần UI (buttons, text fields) mà không cần client phải biết chi tiết về hệ điều hành hoặc cách render cụ thể.
  • Game Development: Trong game, anh em có thể có một EnemyFactory để tạo ra các loại kẻ thù khác nhau (Orc, Goblin, Dragon) dựa trên cấp độ hoặc loại màn chơi hiện tại. Hoặc ItemFactory để tạo ra các vật phẩm (kiếm, giáp, potion).

5. Thử nghiệm và Nên dùng cho case nào?

Anh Creyt đã từng "ngây thơ" viết cả đống if-else để tạo đối tượng, và phải vật lộn mỗi khi có yêu cầu thêm một loại đối tượng mới. Rồi đến lúc "ngộ" ra Factory Pattern, code bỗng trở nên gọn gàng và dễ thở hơn hẳn.

Anh em nên dùng Factory Pattern khi:

  • Khi có nhiều loại đối tượng con (subclasses) cần được tạo ra từ một interface hoặc abstract class chung, và việc lựa chọn đối tượng con nào lại phụ thuộc vào các điều kiện tại runtime.
  • Khi muốn giảm sự phụ thuộc của client code vào các lớp cụ thể của sản phẩm. Client chỉ cần làm việc với interface của sản phẩm và Factory.
  • Khi dự đoán được rằng ứng dụng sẽ cần mở rộng với nhiều loại sản phẩm mới trong tương lai. Factory sẽ giúp việc mở rộng này trở nên dễ dàng và ít rủi ro hơn.
  • Khi muốn áp dụng nguyên tắc "Open/Closed Principle": Mở rộng cho các loại sản phẩm mới mà không cần sửa đổi code client hoặc Factory hiện có (đây là lúc anh em nghĩ đến Abstract Factory Pattern hoặc Factory Method Pattern kết hợp với Dependency Injection, nhưng đó là câu chuyện khác rồi).

Factory Pattern là một công cụ mạnh mẽ giúp anh em viết code sạch hơn, dễ bảo trì và mở rộng hơn rất nhiều. Hãy thực hành nó thật nhiều để biến nó thành một phần "phản xạ tự nhiên" trong tư duy lập trình của mình nhé!

Thuộc Series: Java – OOP

Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!

#tech #cyberpunk #laravel
Chỉnh sửa bài viết

Bình luận (0)

Vui lòng Đăng Nhập để Bình luận

Hỗ trợ Markdown cơ bản
Nguyễn Văn A
1 ngày trước

Tính năng này đỉnh quá ad ơi, chờ mãi mới thấy một blog Tiếng Việt có UI/UX xịn như vầy!