Factory Pattern

1. Giới Thiệu

1.1 Định Nghĩa

Factory Pattern là một creational pattern cung cấp interface để tạo đối tượng trong superclass, nhưng cho phép subclasses thay đổi kiểu đối tượng sẽ được tạo.

1.2 Các Loại Factory Pattern

  1. Simple Factory
  2. Factory Method
  3. Abstract Factory

2. Simple Factory

2.1 Cấu Trúc

// Product interface
public interface Animal {
    void makeSound();
}

// Concrete products
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

// Simple factory
public class AnimalFactory {
    public Animal createAnimal(String type) {
        if ("dog".equalsIgnoreCase(type)) {
            return new Dog();
        } else if ("cat".equalsIgnoreCase(type)) {
            return new Cat();
        }
        throw new IllegalArgumentException("Unknown animal type");
    }
}

2.2 Sử Dụng

public class Client {
    public static void main(String[] args) {
        AnimalFactory factory = new AnimalFactory();

        Animal dog = factory.createAnimal("dog");
        dog.makeSound(); // Output: Woof!

        Animal cat = factory.createAnimal("cat");
        cat.makeSound(); // Output: Meow!
    }
}

3. Factory Method

3.1 Cấu Trúc

// Creator
public abstract class PaymentProcessor {
    public abstract Payment createPayment();

    public void processPayment() {
        Payment payment = createPayment();
        payment.validate();
        payment.process();
    }
}

// Concrete creators
public class CreditCardProcessor extends PaymentProcessor {
    @Override
    public Payment createPayment() {
        return new CreditCardPayment();
    }
}

public class PayPalProcessor extends PaymentProcessor {
    @Override
    public Payment createPayment() {
        return new PayPalPayment();
    }
}

// Product interface
public interface Payment {
    void validate();
    void process();
}

// Concrete products
public class CreditCardPayment implements Payment {
    @Override
    public void validate() {
        System.out.println("Validating credit card...");
    }

    @Override
    public void process() {
        System.out.println("Processing credit card payment...");
    }
}

public class PayPalPayment implements Payment {
    @Override
    public void validate() {
        System.out.println("Validating PayPal account...");
    }

    @Override
    public void process() {
        System.out.println("Processing PayPal payment...");
    }
}

3.2 Sử Dụng

public class Client {
    public static void main(String[] args) {
        PaymentProcessor processor = new CreditCardProcessor();
        processor.processPayment();

        processor = new PayPalProcessor();
        processor.processPayment();
    }
}

4. Abstract Factory

4.1 Cấu Trúc

// Abstract factory
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Concrete factories
public class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

public class MacFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacCheckbox();
    }
}

// Abstract products
public interface Button {
    void paint();
}

public interface Checkbox {
    void paint();
}

// Concrete products
public class WindowsButton implements Button {
    @Override
    public void paint() {
        System.out.println("Rendering Windows button");
    }
}

public class MacButton implements Button {
    @Override
    public void paint() {
        System.out.println("Rendering Mac button");
    }
}

public class WindowsCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Rendering Windows checkbox");
    }
}

public class MacCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Rendering Mac checkbox");
    }
}

4.2 Sử Dụng

public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

public class Client {
    public static void main(String[] args) {
        Application app;

        app = new Application(new WindowsFactory());
        app.paint();

        app = new Application(new MacFactory());
        app.paint();
    }
}

5. Ví Dụ Thực Tế

5.1 Database Connection Factory

public interface DatabaseConnection {
    void connect();
    void disconnect();
    void executeQuery(String query);
}

public class MySQLConnection implements DatabaseConnection {
    @Override
    public void connect() {
        System.out.println("Connecting to MySQL...");
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnecting from MySQL...");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing MySQL query: " + query);
    }
}

public class PostgreSQLConnection implements DatabaseConnection {
    @Override
    public void connect() {
        System.out.println("Connecting to PostgreSQL...");
    }

    @Override
    public void disconnect() {
        System.out.println("Disconnecting from PostgreSQL...");
    }

    @Override
    public void executeQuery(String query) {
        System.out.println("Executing PostgreSQL query: " + query);
    }
}

public class DatabaseFactory {
    public static DatabaseConnection createConnection(String type) {
        if ("mysql".equalsIgnoreCase(type)) {
            return new MySQLConnection();
        } else if ("postgresql".equalsIgnoreCase(type)) {
            return new PostgreSQLConnection();
        }
        throw new IllegalArgumentException("Unknown database type");
    }
}

5.2 Document Generator Factory

public interface Document {
    void generate();
    void save();
}

public class PDFDocument implements Document {
    @Override
    public void generate() {
        System.out.println("Generating PDF document...");
    }

    @Override
    public void save() {
        System.out.println("Saving PDF document...");
    }
}

public class WordDocument implements Document {
    @Override
    public void generate() {
        System.out.println("Generating Word document...");
    }

    @Override
    public void save() {
        System.out.println("Saving Word document...");
    }
}

public abstract class DocumentFactory {
    public abstract Document createDocument();

    public void processDocument() {
        Document doc = createDocument();
        doc.generate();
        doc.save();
    }
}

public class PDFFactory extends DocumentFactory {
    @Override
    public Document createDocument() {
        return new PDFDocument();
    }
}

public class WordFactory extends DocumentFactory {
    @Override
    public Document createDocument() {
        return new WordDocument();
    }
}

6. Lợi Ích và Nhược Điểm

6.1 Lợi Ích

  1. Loose coupling giữa creator và concrete products
  2. Single Responsibility Principle
  3. Open/Closed Principle
  4. Dễ dàng mở rộng code
  5. Tập trung logic tạo đối tượng

6.2 Nhược Điểm

  1. Code có thể trở nên phức tạp
  2. Cần tạo nhiều class mới
  3. Khó debug với factory methods
  4. Khó thay đổi toàn bộ product family

7. Best Practices

7.1 Dependency Injection

@Configuration
public class FactoryConfig {
    @Bean
    public PaymentFactory paymentFactory() {
        Map<String, Payment> paymentMap = new HashMap<>();
        paymentMap.put("credit", new CreditCardPayment());
        paymentMap.put("paypal", new PayPalPayment());
        return new PaymentFactory(paymentMap);
    }
}

@Service
public class PaymentService {
    private final PaymentFactory paymentFactory;

    @Autowired
    public PaymentService(PaymentFactory paymentFactory) {
        this.paymentFactory = paymentFactory;
    }

    public void processPayment(String type) {
        Payment payment = paymentFactory.createPayment(type);
        payment.process();
    }
}

7.2 Exception Handling

public class FactoryException extends RuntimeException {
    public FactoryException(String message) {
        super(message);
    }

    public FactoryException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class PaymentFactory {
    private final Map<String, Payment> paymentMap;

    public PaymentFactory(Map<String, Payment> paymentMap) {
        this.paymentMap = paymentMap;
    }

    public Payment createPayment(String type) {
        Payment payment = paymentMap.get(type);
        if (payment == null) {
            throw new FactoryException(
                "Unknown payment type: " + type);
        }
        return payment;
    }
}

8. Testing

8.1 Unit Testing

@ExtendWith(MockitoExtension.class)
class PaymentFactoryTest {
    @InjectMocks
    private PaymentFactory paymentFactory;

    @Mock
    private Map<String, Payment> paymentMap;

    @Test
    void whenValidType_thenCreatePayment() {
        // Given
        String type = "credit";
        Payment payment = mock(Payment.class);
        when(paymentMap.get(type)).thenReturn(payment);

        // When
        Payment result = paymentFactory.createPayment(type);

        // Then
        assertNotNull(result);
        assertSame(payment, result);
    }

    @Test
    void whenInvalidType_thenThrowException() {
        // Given
        String type = "invalid";
        when(paymentMap.get(type)).thenReturn(null);

        // When/Then
        assertThrows(FactoryException.class, () ->
            paymentFactory.createPayment(type));
    }
}

8.2 Integration Testing

@SpringBootTest
class PaymentServiceIntegrationTest {
    @Autowired
    private PaymentService paymentService;

    @Test
    void whenProcessPayment_thenSuccess() {
        // Given
        String type = "credit";

        // When/Then
        assertDoesNotThrow(() ->
            paymentService.processPayment(type));
    }

    @Test
    void whenProcessInvalidPayment_thenThrowException() {
        // Given
        String type = "invalid";

        // When/Then
        assertThrows(FactoryException.class, () ->
            paymentService.processPayment(type));
    }
}

9. References

9.1 Books

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Head First Design Patterns
  • Clean Code
  • Effective Java

9.2 Online Resources