설계/디자인 패턴

[디자인패턴] 생성 패턴 : 팩토리 메서드 패턴

mint* 2025. 2. 12. 23:47
728x90

생성 패턴 : 팩토리 메서드 패턴

팩토리 메서드 패턴이란?

  • 생성과 구현의 분리
    • 객체 생성을 위한 인터페이스(or 상위 클래스)를 정의하고, 실제 어떤 클래스가 생성될지는 구현 클래스(or 하위 클래스)가 결정한다.
    • 어떤 구현 객체가 실제 생성되었는지 Client는 알지 못한다. (결합도가 낮다.)

 

팩토리 메서드 패턴이 필요한 이유

  • 모든 코드가 특정 객체에 결합도가 높게 작성되었다면, 다른 객체가 해당 객체를 대체하거나 새로운 객체를 포함할 때 많은 코드를 변경해야 한다.
    • 공통 로직마다 구현체별로 분기 처리가 필요하다.
  • 팩토리 메서드 패턴을 이용하면 Client는 인터페이스만 참조하고 특정 구현체에 종속되어있지 않으므로 확장성이 커진다.
    • 새로운 객체가 추가되거나 대체되더라도 인터페이스가 변하지 않는 이상 다른 코드에 영향을 주지 않는다. (OCP)

 

팩토리 메서드 특징

출처 : https://refactoring.guru/design-patterns/factory-method

  • 모든 구현체들은 특정 인터페이스를 공통적으로 따라야 한다.

 

구조

출처 : https://refactoring.guru/design-patterns/factory-method

Creator

  • 인터페이스 또는 상위 클래스
  • 객체를 반환하는 방법을 정의한다.

ConcreateCreator

  • Creator 구현체
  • 특정 객체를 생성하여 반환한다.

Product

  • 생성할 객체의 인터페이스

ConcreteProduct

  • 생성할 객체의 실제 구현체

 

예제 코드

출처 : https://refactoring.guru/design-patterns/factory-method

상속을 이용한 예제 코드

guru 사이트의 수도 코드를 java로 변경하였습니다.

// Creator
abstract class Dialog {
    // 팩토리 메서드 (추상 메서드)
    abstract Button createButton();

    public void render() {
        Button okButton = createButton();
        okButton.onClick(() -> closeDialog());
        okButton.render();
    }

    private void closeDialog() {
        System.out.println("Dialog closed");
    }
}

// Concrete creator
class WindowsDialog extends Dialog {
    @Override
    Button createButton() {
        return new WindowsButton();
    }
}

class WebDialog extends Dialog {
    @Override
    Button createButton() {
        return new HTMLButton();
    }
}

// Product (interface) 
interface Button {
    void render();
    void onClick(Runnable f);
}

// Concrete product
class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Render Windows button");
    }

    @Override
    public void onClick(Runnable f) {
        System.out.println("Bind native Windows click event");
        f.run();
    }
}

class HTMLButton implements Button {
    @Override
    public void render() {
        System.out.println("Render HTML button");
    }

    @Override
    public void onClick(Runnable f) {
        System.out.println("Bind web browser click event");
        f.run();
    }
}

class Config {
    private String OS;

    public Config(String OS) {
        this.OS = OS;
    }

    public String getOS() {
        return OS;
    }
}

// 실행 부분
class Application {

    private Dialog dialog;

    public void initialize() throws Exception {
        Config config = readApplicationConfigFile();

        if (config.getOS().equals("Windows")) {
            dialog = new WindowsDialog();
        } else if (config.getOS().equals("Web")) {
            dialog = new WebDialog();
        } else {
            throw new Exception("Error! Unknown operating system.");
        }
    }

    private Config readApplicationConfigFile() {
        return new Config("Windows");
    }

    public void main() {
        try {
            initialize();
            dialog.render();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class FactoryMethodDemo {
    public static void main(String[] args) {
        Application app = new Application();
        app.main();
    }
}

Refactoring Guru 사이트 예제에서는 상속을 이용했지만, 합성을 이용하면 더 유연하기 때문에
합성으로 구현해봅시다.

 

합성으로 구현

// Creator
interface ButtonFactory {
    // 팩토리 메서드 
    Button createButton();  
}

// Concrete Creator
class WindowsButtonFactory implements ButtonFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
}

class WebButtonFactory implements ButtonFactory {
    @Override
    public Button createButton() {
        return new HTMLButton();
    }
}

// Product
interface Button {
    void render();
    void onClick(Runnable f);
}

// Concrete Product
class WindowsButton implements Button {
    @Override
    public void render() { }

    @Override
    public void onClick(Runnable f) { }
}

class HTMLButton implements Button {
    @Override
    public void render() { }

    @Override
    public void onClick(Runnable f) { }
}

// Dialog는 이제 Creator가 아닌 Client가 됨
class Dialog {

    private final ButtonFactory buttonFactory;  // Creator를 합성으로 가진다.

    public Dialog(ButtonFactory buttonFactory) {
        this.buttonFactory = buttonFactory;
    }

    public void render() {
        Button button = buttonFactory.createButton();
        button.onClick(() -> closeDialog());
        button.render();
    }

    private void closeDialog() { }
}
  • 상속으로 변경하면 런타임에 구현체인 Factory를 변경할 수 있다.
  • Dialog에서 더이상 버튼 생성 책임을 가지지 않아 단일 책임 원칙(SRP)을 더 잘 지킨다.

 

적용점

  • 코드에서 사용되는 객체의 정확한 유형과 종속성을 알지 못할 경우 사용한다.
    • 실제 구현체와 느슨하게 결합하여 확장성이 높다.
  • 라이브러리 또는 프레임워크 사용자에게 내부 구성 요소를 확장 가능하도록 만들고 싶을 때 사용한다.
  • 생성 비용이 무거울 경우, 시스템 리소스를 재사용할 때 사용 가능하다.
    • 매번 객체를 생성하지 않고, 팩토리에서 가져와 사용할 수 있다.
class DatabaseConnectionFactory {

    // 팩토리 메서드
    public DatabaseConnection getConnection() {
        // 1. 사용 가능한 기존 연결이 있는지 확인
        for (DatabaseConnection conn : connectionPool) {
            if (!conn.isInUse()) {
                conn.setInUse(true);
                return conn;
            }
        }

        // 2. 새 연결 생성하기/..

    }
}

 

Reference

https://refactoring.guru/design-patterns/factory-method

 

Factory Method

/ Design Patterns / Creational Patterns Factory Method Also known as: Virtual Constructor Intent Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objec

refactoring.guru

 

 

 

728x90