[Design Pattern] Abstract Factory, Factory Method

steadfastree·2024년 9월 25일
0

Abstract Factory Pattern

Abstract Factory Pattern은 관련된 객체군을 생성하기 위한 일관적인 인터페이스를 제공하는 패턴이다.

구조

  • AbstractFactory: Product 객체를 생성하는 추상 메서드를 선언
  • ConcreteFactory: AbstractFactory를 구현하여 구체적인 Product 객체를 생성
  • AbstractProduct: Product의 인터페이스 정의
  • ConcreteProduct: 구체적인 Product 구현

예시

// AbstractProduct - Button
interface Button {
    void paint();
}

// ConcreteProduct - WindowsButton
class WindowsButton implements Button {
    @Override
    public void paint() {
        System.out.println("Windows 스타일의 버튼을 그립니다.");
    }
}

// ConcreteProduct - MacButton
class MacButton implements Button {
    @Override
    public void paint() {
        System.out.println("Mac 스타일의 버튼을 그립니다.");
    }
}

// AbstractProduct - Checkbox
interface Checkbox {
    void paint();
}

// ConcreteProduct - WindowsCheckbox
class WindowsCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Windows 스타일의 체크박스를 그립니다.");
    }
}

// ConcreteProduct - MacCheckbox
class MacCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Mac 스타일의 체크박스를 그립니다.");
    }
}

// AbstractFactory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

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

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

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

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

// Client
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 Main {
    public static void main(String[] args) {
        GUIFactory factory;
        
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("win")) {
            factory = new WindowsFactory();
        } else {
            factory = new MacFactory();
        }

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

위의 예시를 통해 추상 팩토리 패턴의 장단점을 살펴보자.

장점

1. 구체적인 클래스 없이 인터페이스만으로 객체를 생성할 수 있게 된다

Application Class를 확인해보자.
WindowsFactory나 MacFactory와 같은 구체적인 클래스 없이 인터페이스만 받아서 객체를 생성할 수 있게 된다.

2. Product 사이의 일관성이 지켜진다.

Button과 같이, 하나의 AbstractProduct가 정의된다면, ConcreteProduct는 이 AbstractProduct를 구현하여야 하므로 Product 사이의 일관성이 지켜진다.

3. Product군을 쉽게 대체 가능하다.

다시, Application Class를 확인해보자.

Application은 특정한 ConcreteFactory에 의존하지 않고,
AbstractFactory와 그에 의해 만들어지는 AbstractProduct군에 의존하고 있다.

따라서 WindowsFactory에 의해 생성된 팩토리를 넘겨주다가
MacFactory에 의해 생성된 팩토리를 넘겨준다 하여도, 추가적인 코드 수정이 필요없이 쉽게 대체가 가능해진다.

4. 단일 책임 원칙(SRP)와 개방/폐쇄 원칙(OCP)를 지원한다.

단일 책임 원칙은 객체가 오직 하나의 책임만 가진다는 원칙이다.

  • 예를 들어, MacFactory라는 ConcreteFactory에서, 해당 객체는 Mac과 관련된 ConcreteProduct 생성이라는 책임만 지닌다.

개방 폐쇄 원칙은 확장에는 열려있고, 수정에는 닫혀있도록 하는 원칙이다.

  • 예를 들어 LinuxFactory라는 새로운 ConcreteFactory를 추가하고자 할 때, 기존 코드를 수정할 필요 없이 해당하는 ConcreteFactory와 ConcreteProduct만 추가해주면 기능을 확장할 수 있다.

단점

새로운 Product를 Factory에 추가하고자 할 때는 기존의 모든 ConcreteFactory 또한 수정해주어야 하므로, 새로운 Product를 제공하는 데에는 제약이 있다.

이러한 장단점을 고려해볼 때, 제공하는 객체군은 변하지만(예시 : Windows, Mac, Linux), 그 안에 들어있는 Product 자체는 변화가 크게 없는 경우, Abstract Factory Pattern의 도입을 고려해볼 수 있겠다.

Factory Method Pattern

Factory Method 패턴은 객체 생성을 서브클래스에 위임하는 패턴이다. 이 패턴을 사용하면 객체 생성의 유연성을 높이고, 코드의 재사용성과 유지보수성을 향상시킬 수 있다.

구조

  1. Product: 팩토리 메서드가 생성하는 객체의 인터페이스
  2. ConcreteProduct: Product 인터페이스의 구체적인 구현
  3. Creator: 팩토리 메서드를 선언하는 추상 클래스
  4. ConcreteCreator: 팩토리 메서드를 구현하여 ConcreteProduct 인스턴스를 반환

예시

// Product
interface Logger {
    void log(String message);
}

// ConcreteProduct
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Console: " + message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("File: " + message);
    }
}

// Creator
abstract class LoggerFactory {
    public void logMessage(String message) {
        Logger logger = createLogger();
        logger.log(message);
    }

    // Factory Method
    protected abstract Logger createLogger();
}

// ConcreteCreator
class ConsoleLoggerFactory extends LoggerFactory {
    @Override
    protected Logger createLogger() {
        return new ConsoleLogger();
    }
}

class FileLoggerFactory extends LoggerFactory {
    @Override
    protected Logger createLogger() {
        return new FileLogger();
    }
}

// Client code
public class Main {
    public static void main(String[] args) {
        LoggerFactory factory = new ConsoleLoggerFactory();
        factory.logMessage("Hello, Factory Method!");

        factory = new FileLoggerFactory();
        factory.logMessage("Hello, Factory Method!");
    }
}

LoggerFactory라는 Creator의 동작을 살펴보자면,

이 Creator가 실제로 어떤 ConcreteProduct를 생성할 지에 대해서는, 이 LoggerFactory를 상속받아 구현될 ConcreteCreator(ConsoleLoggerFactory, FileLoggerFactory)에 위임한다.

LoggerFactory에서는 생성된 Product를 어떻게 사용할지에 대해서만 관심을 가지고 있다.

logMessage라는 메서드는 Logger라는 Product에 있는 log라는 메서드를 실행시키도록 한다.

Product의 생성과 그것의 이용에 대한 관심을 잘 분리하고 있다.

위의 예시를 통해 팩토리 메서드 패턴의 장단점을 살펴보자.

장점

1. 구체적인 클래스 없이 인터페이스만으로 객체를 생성할 수 있게 된다

LoggerFactory를 확인해보자.
ConsoleLogger나 FileLogger와 같은 구체적인 클래스 없이 인터페이스만 받아서 객체를 생성할 수 있게 된다.

2. 단일 책임 원칙(SRP)와 개방/폐쇄 원칙(OCP)를 지원한다.

단일 책임 원칙은 객체가 오직 하나의 책임만 가진다는 원칙이다.

  • 예를 들어, ConsoleLoggerFactory라는 ConcreteCreator에서, 해당 객체는 ConsoleLogger라는 ConcreteProduct 생성이라는 책임만 지닌다.

개방 폐쇄 원칙은 확장에는 열려있고, 수정에는 닫혀있도록 하는 원칙이다.

  • 예를 들어 DatabaseLoggerFactory라는 새로운 ConcreteCreator를 추가하고자 할 때, 기존 코드를 수정할 필요 없이 해당하는 ConcreteProduct와 ConcreteCreator만 추가해주면 기능을 확장할 수 있다.

단점

1. 과도한 클래스 수

Product마다 그에 해당하는 Creator 서브클래스를 만들어야 하므로 클래스 수가 과도하게 늘어날 수가 있다.

  • DatabaseLogger, NetworkLogger 등을 추가한다면, 그에 해당하는 LoggerFactory를 일일히 만들어주어야 한다.

2. 복잡성

간단한 객체를 만드는 데에도 Factory Method를 적용하면 불필요한 복잡성이 발생할 수 있다

3. 제품군 처리의 한계

Abstract Factory와 다르게, 관련된 여러 Product를 한번에 생성하는 데에는 적합하지 않고, 하나의 Product에 대해 여러 ConcreteProduct를 생성할 때에만 유리하다.

profile
개인기록

0개의 댓글