공부/디자인 패턴

[디자인패턴] 팩토리 매서드 (Factory Method Pattern)

감자 바보 2022. 4. 2. 16:36
반응형

팩토리 매서드 패턴 (Factory Method Pattern)

 

의도 : 객체 생성을 위한 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스가 내리도록 한다.

  • 팩토리 메서드는 객체를 생성하고 반환하는 메서드이다. 하위 클래스에서 이를 오버라이딩하여 사용한다.

 

활용

  • 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측 할 수 없을 경우
  • 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 경우
  • 객체 생성의 책임을 몇 개의 보조 서브클래스 가운데 하나에게 위임하고 어떤 서브클래가 가운데 하나에게 위임하고, 어떤 서브클래스가 위임자인지에 대한 정보를 국소화시키고 싶을 경우

 

구조

Product : 팩토리 매서드가 생성하는 객체의 인터페이스 정의

ConcreteProduct : 팩토리 매서드가 생성하는 객체

Creator : Product 타입 객체 반환하는 팩터리 메서드 인터페이스 선언

ConcreteCreator : 팩토리 메서드 재정의하여 ConcreteProduct 인스턴스 생성 후 반환

  • Creator는 서브클래스에 실제 필요한 메서드를 정의하여 필요한 ConcreteProduct의 인스턴스를 반환할 수 있도록 한다.

 

결과

  1. 서브클래스에 대한 훅(hook) 메서드를 제공한다.
    • 훅(hook) 메소드란 아무일도 하지 않거나 기본 동작을 정의하는 메소드이다. 서브클래스에서 이를 오버라이딩하여 사용할 수 있다. 
  2. 병렬적인 클래스 계통을 연결하는 역할을 수행한다.

 

구현

 

구현 방법은 크게 두가지이다.

  1. Creator 클래스를 추상 클래스로 정의하고 정의한 팩토리 메서드에 대한 구현은 제공하지 않는 경우
  2. Creator 클래스가 구체 클래스이고, 팩토리 메서드에 대한 기본 구현을 제공하는 경우

 매개변수를 통해 팩토리 매서드가 여러 종류의 제품을 생성하게 할 수도 있다. 

 지연 초기화(Lazy Initialization), 제네릭(Generic)을 사용하여 구현할 수도 있다.

 

예시


 추상 팩토리 패턴의 예제였던 핸드폰 생성 예제를 팩토리 매서드에 맞게 변형시켰다. PhoneFactory 클래스는 팩토리 메서드인 makeDisplay와 makeInnerDevice를 가지고 있다. makeDisplay는 매개변수를 받고 매개변수에 따라 필요한 디스플레이(LG, 삼성, 애플)를 생성하여 반환해준다. 반면 makeInnerDevice는 클래스별로 미리 지정된 InnerDevice를 생성하여 반환한다. 

 

UML

팩토리 메소드로 생성되는 객체

Display.java

더보기
public abstract class Display {
    public Display() {
    }
    public abstract void testDisplay();
}

class LgDisplay extends Display {
    public LgDisplay() {}

    @Override
    public void testDisplay() {
        System.out.println("LG 디스플레이 테스트");
    }
}

class SamsungDisplay extends Display {
    public SamsungDisplay() {}

    @Override
    public void testDisplay() {
        System.out.println("삼성 디스플레이 테스트");
    }
}

class AppleDisplay extends Display {
    public AppleDisplay() {}

    @Override
    public void testDisplay() {
        System.out.println("애플 디스플레이 테스트");
    }
}

InnerDevice.java

더보기
public class InnerDevice {
    public InnerDevice() {
    }

    public void testKeyboardInput() {
        System.out.println("기본 키보드 입력 테스트");
    }

    public void testMicrophoneInput() {
        System.out.println("기본 마이크 입력 테스트");
    }
}

class SamsungInnerDevice extends InnerDevice {
    public SamsungInnerDevice() {
    }

    @Override
    public void testKeyboardInput() {
        System.out.println("삼성 키보드 입력 테스트");
    }

    @Override
    public void testMicrophoneInput() {
        System.out.println("삼성 마이크 입력 테스트");
    }
}

class AppleInnerDevice extends InnerDevice {
    public AppleInnerDevice() {
    }

    @Override
    public void testKeyboardInput() {
        System.out.println("애플 키보드 입력 테스트");
    }

    @Override
    public void testMicrophoneInput() {
        System.out.println("애플 마이크 입력 테스트");
    }
}

Phone.java

더보기
public class Phone {
    private Display display;
    private InnerDevice innerDevice;

    public void setDisplay(Display display) {
        this.display = display;
    }

    public Display getDisplay() {
        return display;
    }

    public void setInnerDevice(InnerDevice innerDevice) {
        this.innerDevice = innerDevice;
    }

    public InnerDevice getInnerDevice() {
        return innerDevice;
    }
}

 

팩토리 메서드를 가지는 객체

PhoneFactory.java

더보기
public class PhoneFactory {
    PhoneFactory() {}

    //An Operation
    public Phone createPhone(int item) {
        Phone phone = new Phone();
        phone.setInnerDevice(makeInnerDevice());
        phone.setDisplay(makeDisplay(item));
        return phone;
    }

    //Factory Method
    public Display makeDisplay(int item) {
        if (item == 0) return new LgDisplay();
        else return null;
    }

    public InnerDevice makeInnerDevice() {
        return new InnerDevice();
    }

}

class SamsungPhoneFactory {
    SamsungPhoneFactory() {}

    //An Operation
    public Phone createPhone(int item) {
        Phone phone = new Phone();
        phone.setInnerDevice(makeInnerDevice());
        phone.setDisplay(makeDisplay(item));
        return phone;
    }

    //Factory Method
    public Display makeDisplay(int item) {
        if (item == 0) return new LgDisplay();
        if (item == 1) return new SamsungDisplay();
        if (item == 2) return new AppleDisplay();
        else return null;
    }

    public InnerDevice makeInnerDevice() {
        return new SamsungInnerDevice();
    }

}


class ApplePhoneFactory {
    ApplePhoneFactory() {}

    //An Operation
    public Phone createPhone(int item) {
        Phone phone = new Phone();
        phone.setInnerDevice(makeInnerDevice());
        phone.setDisplay(makeDisplay(item));
        return phone;
    }

    //Factory Method
    public Display makeDisplay(int item) {
        if (item == 0) return new LgDisplay();
        if (item == 1) return new SamsungDisplay();
        if (item == 2) return new AppleDisplay();
        else return null;
    }

    public InnerDevice makeInnerDevice() {
        return new AppleInnerDevice();
    }

}

 

메인 함수

TestFactoryMethodPattern.java

더보기
public class TestFactoryMethodPattern {
    public static void main(String[] args) {
        PhoneFactory basicPhoneFactory = new PhoneFactory();
        SamsungPhoneFactory samsungPhoneFactory = new SamsungPhoneFactory();
        ApplePhoneFactory applePhoneFactory = new ApplePhoneFactory();

        Phone phone;

        System.out.println("기본 핸드폰 생성");
        System.out.println("LG 디스플레이 사용");
        phone = basicPhoneFactory.createPhone(0);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n삼성 디스플레이 사용");
        phone = basicPhoneFactory.createPhone(1);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n애플 디스플레이 사용");
        phone = basicPhoneFactory.createPhone(2);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n삼성 핸드폰 생성");
        System.out.println("LG 디스플레이 사용");
        phone = samsungPhoneFactory.createPhone(0);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n삼성 디스플레이 사용");
        phone = samsungPhoneFactory.createPhone(1);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n애플 디스플레이 사용");
        phone = samsungPhoneFactory.createPhone(2);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n애플 핸드폰 생성");
        phone = applePhoneFactory.createPhone(0);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n삼성 디스플레이 사용");
        phone = applePhoneFactory.createPhone(1);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();

        System.out.println("\n애플 디스플레이 사용");
        phone = applePhoneFactory.createPhone(2);
        phone.getDisplay().testDisplay();
        phone.getInnerDevice().testKeyboardInput();
        phone.getInnerDevice().testMicrophoneInput();
    }
}

 

실행결과

실행 결과 - 좌측부터 기본, 삼성, 애플 핸드폰

마무리


 GoF의 디자인 패턴을 읽고도 추상 팩토리 패턴과 팩토리 메서드 패턴의 차이점을 정확히 알기 어려워 다른 자료를 찾아보며 고민해봤다. 

 내가 이해한 차이점은 추상 팩토리 패턴은 한 클래스가 특정한 조건을 만족하는 한 종류의 객체만을 생성하여 반환하도록 강제한다. 따라서 객체군을 생성하는데 유용한 패턴이다. 이전 추상클래스 포스트 예제에서 SamsungPhoneFactory는 삼성 Display와 InnerDevice만을 생성하였으며 ApplePhoneFactory는 애플 디스플레이와 InnerDevice만을 생성하였다.

 하지만 팩토리 메서드 패턴은 이와 달리 한 클래스가 조건을 만족하는 여러 종류의 객체들을 생성할 수 있다. 위 예제를 예를 들면 PhoneFactory 객체는 InnerDevice만 자신 메이커의 부품을 사용하고 Display는 매개변수를 받아 그에 맞는 Display(LG, 삼성, 애플)를 생성하여 반환한다. 그로 인해 한 클래스가 여러 종류의 객체를 생성 및 반환한다는 차이가 있다. 

 

 팩토리 메서드는 어떤 인스턴스를 생성할 지 서브 클래스에서 지정하다는 점이 중요하다.

 

참고

에릭 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴』, 프로텍 미디어, 2015

후크 메소드 (Hook Method) (tistory.com)