반응형

의도

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

 

활용성

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

 

결과

  • 응용프로그램에 국한된 클래스가 코드에 종속되지 않도록 해준다.
  • 서브클래스에 대한 훅(hook) 메서드를 제공한다.
  • 병렬적인 클래스 계통을 연결하는 역할을 담당한다.
    • 병렬적인 클래스 계통은 클래스가 자신의 책임을 분리된 다른 클래스에 위임할 때 발생한다.
    • 예) 그래픽 객체 - 선의 길이, 글자 크기에 대한 상태는 그림 객체가 아니라 이를 별도로 관리하는 객체를 사용
  • 잠재적인 단점으로 사용자가 ConcreteProduct 객체 하나만 만들려 할 때에도 Creator 클래스를 서브클래싱해야 할지 모른다는 점이다.
    • 이로 인해서 클래스 계통의 부피가 확장되는 문제가 생길 수 있다.

 

협력 방법

  • Creator는 자신의 서브클래스를 통해 실제 필요한 팩토리 메서드를 정의하여 적절한 ConcreteProduct의 인스턴스를 반환할 수 있게 한다.

 

구현 고려사항

  • 객체를 제조하는 방법을 아는 메서드를 팩토리 메서드라고 한다.
  • 팩토리 패턴을 구현하는 방식은 크게 두 가지
    1. Creator 클래스를 추상 클래스로 정의하고, 정의한 팩토리 메서드에 대한 구현은 제공하지 않는 경우
      1. 추상 클래스로 정의할 때는 구현을 제공한 서브클래스를 반드시 정의해야한다.
      1. 아직 예측할 수 없는 클래스들을 생성해야 하는 문제가 생긴다.
    1. Creator가 구체 클래스이고, 팩토리 메서드에 대한 기본 구현을 제공하는 경우
      1. 구체 클래스로 정의할 때는 Creator가 팩토리 메서드를 사용하여 유연성을 보장할 수 있다.
        • 구체화된 Product를 기본적으로 생성하고 필요할 경우 ConcreteCreator를 이용해서 구체화된 Product를 변경할 수 있다.
  • 또 다른 방식으로 팩토리 메서드를 매개변수화 하는 경우
    • 팩토리 메서드를 이용하여 여러 종류의 제품을 생성한다.
    • 팩토리 메서드는 매개변수를 통하여 생성해야할 제품을 판단한다.
    • 팩토리 메서드가 생성하는 모든 객체는 Product 인터페이스를 만족해야 한다.
    • 매개변수화된 팩토리 메서드를 오버라이드하면 Creator 클래스가 생성하는 제품을 쉽게 확장하거나 변경할 수 있다.
      • 단, 제품의 다양화를 위해 서브클래스를 만들어야하는 단점이 있다.
      • 이런 문제를 해결할 수 있는 방법 중 하나는 Creator 클래스의 서브클래스가 되는 템플릿 클래스를 정의하고 이것이 Product 클래스로 매개변수화되도록 만드는 것이다.
      • 템플릿 클래스를 이용하면 사용자는 Creator를 상속받는 서브클래스를 정의할 필요 없이, 적절한 Product 클래스만 준비하면 된다.

구조

 

구현 - Creator를 추상 클래스로 정의한 경우

실제 구현 구조

 

소스코드

/**
 * Creator 를 추상적으로 정의하고 팩토리 메서드에 대한 구현 미제공
 * Product(MyDocument, YourDocument, ...) 추가하려 할 때마다 서브클래싱 필요
 * -> 클래스 계통 부피 확장
 **/
//Creator
public interface Application {
    Document createDocument();//팩토리 메서드
    default void newDocument(){
        System.out.println("Application.newDocument");
    }
    default void openDocument(){
        System.out.println("Application.openDocument");
    }
}
//ConcreteCreator
public class MyApplication implements Application {
    @Override
    public Document createDocument() {//팩토리 메서드
        return new MyDocument();
    }
}
//ConcreteCreator
public class YourApplication implements Application {
    @Override
    public Document createDocument() {//팩토리 메서드
        return new YourDocument();
    }
}
//Product
public interface Document {
    void open();
    void close();
    void save();
    void revert();
}
//ConcreteProduct
public class MyDocument implements Document {
    @Override
    public void open() {
        System.out.println("MyDocument.open");
    }

    @Override
    public void close() {
        System.out.println("MyDocument.close");
    }

    @Override
    public void save() {
        System.out.println("MyDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("MyDocument.revert");
    }
}
//ConcreteProduct
public class YourDocument implements Document {
    @Override
    public void open() {
        System.out.println("YourDocument.open");
    }

    @Override
    public void close() {
        System.out.println("YourDocument.close");
    }

    @Override
    public void save() {
        System.out.println("YourDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("YourDocument.revert");
    }
}
public class Main {
    public static void main(String[] args) {
        Application myApplication = new MyApplication();
        Document document = myApplication.createDocument();
        document.open();
        document.close();

        myApplication = new YourApplication();
        document = myApplication.createDocument();
        document.open();
        document.close();
    }
}
  • 추상적인 Factory Method를 사용하는 Creator는 Product 구현에 대해 아무것도 모르고 모든 것을 ConcreteCreator에 맡긴다.

 

구현 - Creator를 구체 클래스로 정의한 경우

실제 구현 구조

 

소스코드

/**
 * Creator 를 구체 클래스로 정의하고 팩토리 메서드에 대한 구현 제공
 * 구체적으로 자신이 생성할 제품을 알고있다.
 * -> Creator의 팩토리 메서드를 통해 제품을 생성할 수 있다.
 **/
//Creator
public class Application {
    public Document createDocument() {//팩토리 메서드
        return new MyDocument();
    }
    public void newDocument() {
        System.out.println("Application.newDocument");
    }
    public void openDocument() {
        System.out.println("Application.openDocument");
    }
}
/**
 * Creator가 알고 있는 제품을 변경할 수 있다.
 */
//ConcreteCreator
public class YourApplication extends Application {
    @Override
    public Document createDocument() {
        return new YourDocument();
    }
}
//Product
public interface Document {
    void open();
    void close();
    void save();
    void revert();
}
//ConcreteProduct
public class MyDocument implements Document {
    @Override
    public void open() {
        System.out.println("MyDocument.open");
    }

    @Override
    public void close() {
        System.out.println("MyDocument.close");
    }

    @Override
    public void save() {
        System.out.println("MyDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("MyDocument.revert");
    }
}
//ConcreteProduct
public class YourDocument implements Document {
    @Override
    public void open() {
        System.out.println("YourDocument.open");
    }

    @Override
    public void close() {
        System.out.println("YourDocument.close");
    }

    @Override
    public void save() {
        System.out.println("YourDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("YourDocument.revert");
    }
}
public class Main {
    public static void main(String[] args) {
        Application application = new Application();
        Document document = application.createDocument();
        document.open();
        document.close();
    }
}
  • 기본 Factory Method를 사용하는 Creator는 대부분의 경우 원하는 제품 구현을 알고 있지만 항상 그런 것은 아니다. 따라서 Concrete Creator가 기본값을 재정의할 수 있다.

 

구현 - 팩토리 메서드를 매개변수화 하는 경우

실제 구현 구조

 

소스코드

/**
 * 팩토리 메소드의 매개변수화
 * 여러 종류의 제품을 생성할 수 있다.
 * 매개변수를 통해서 생성할 제품을 결정한다.
 * 매개변수화된 팩토리 메서드를 오버라이드하면, Creator 클래스가 생성하는 제품을 쉽게 확장하거나 변경할 수 있다.
 **/
//Creator
public interface Application {
    Document createDocument(String id);//팩토리 메서드
    default void newDocument(){
        System.out.println("Application.newDocument");
    }
    default void openDocument(){
        System.out.println("Application.openDocument");
    }
}
//ConcreteCreator
public class MyApplication implements Application {
    @Override
    public Document createDocument(String id) {//팩토리 메서드
        if(id.equals("my"))  return new MyDocument();
        if(id.equals("your"))  return new YourDocument();

        return new DefaultDocument();
    }
}
//ConcreteCreator
public class YourApplication implements Application {
    @Override
    public Document createDocument(String id) {//팩토리 메서드
        if(id.equals("my")) return new YourDocument();
        if(id.equals("your")) return new MyDocument();

        return new DefaultDocument();
    }
}
//Product
public interface Document {
    void open();
    void close();
    void save();
    void revert();
}
//ConcreteProduct
public class DefaultDocument implements Document {
    @Override
    public void open() {
        System.out.println("DefaultDocument.open");
    }

    @Override
    public void close() {
        System.out.println("DefaultDocument.close");
    }

    @Override
    public void save() {
        System.out.println("DefaultDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("DefaultDocument.revert");
    }
}
//ConcreteProduct
public class MyDocument implements Document {
    @Override
    public void open() {
        System.out.println("MyDocument.open");
    }

    @Override
    public void close() {
        System.out.println("MyDocument.close");
    }

    @Override
    public void save() {
        System.out.println("MyDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("MyDocument.revert");
    }
}
//ConcreteProduct
public class YourDocument implements Document {
    @Override
    public void open() {
        System.out.println("YourDocument.open");
    }

    @Override
    public void close() {
        System.out.println("YourDocument.close");
    }

    @Override
    public void save() {
        System.out.println("YourDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("YourDocument.revert");
    }
}
public class Main {
    public static void main(String[] args) {
        Application myApplication = new MyApplication();
        Document document = myApplication.createDocument("my");
        document.open();
        document.close();

        document = myApplication.createDocument("theirs");
        document.open();
        document.close();

        myApplication = new YourApplication();
        document = myApplication.createDocument("my");
        document.open();
        document.close();

        document = myApplication.createDocument("theirs");
        document.open();
        document.close();
    }
}
  • 매개변수화된 Factory Method를 사용하는 Creator에는 선택할 제품 구현 메뉴가 있으며 ConcreteCreator에 요청할 제품을 결정한다.

 

구현 - 템플릿을 이용하여 서브클래싱 피하기

실제 구현 구조

소스코드

/**
 * 템플릿을 사용하여 팩토리 메서드의 서브클래싱 피하기
 **/
//Creator
public interface Application {
    Document createDocument();//팩토리 메서드
    default void newDocument(){
        System.out.println("Application.newDocument");
    }
    default void openDocument(){
        System.out.println("Application.openDocument");
    }
}
/**
 * Creator 클래스의 서브클래스가 되는 템플릿 클래스를 이용하여 Product를 추가할 때 마다 서브클래싱하는 문제를 해결한다.
 * 템플릿 클래스를 이용하면 Creator를 상속받는 서브클래스를 추가로 정의할 필요없이 적절한 Product 클래스만 준비하면 된다.
 */
//ConcreteCreator
public class StandardApplication<T extends Document> implements Application {
    private Class<T> t;

    public StandardApplication(Class<T> t) {
        this.t = t;
    }

    @Override
    public Document createDocument() {
        Document document = null;
        try {
            document = this.t.getDeclaredConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return document;
    }
}
//Product
public interface Document {
    void open();
    void close();
    void save();
    void revert();
}
//ConcreteProduct
public class MyDocument implements Document {
    @Override
    public void open() {
        System.out.println("MyDocument.open");
    }

    @Override
    public void close() {
        System.out.println("MyDocument.close");
    }

    @Override
    public void save() {
        System.out.println("MyDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("MyDocument.revert");
    }
}
//ConcreteProduct
public class YourDocument implements Document {
    @Override
    public void open() {
        System.out.println("YourDocument.open");
    }

    @Override
    public void close() {
        System.out.println("YourDocument.close");
    }

    @Override
    public void save() {
        System.out.println("YourDocument.save");
    }

    @Override
    public void revert() {
        System.out.println("YourDocument.revert");
    }
}
//컴파일 에러 검출용도
public class DefaultDocument {
    public void open() {
        System.out.println("DefaultDocument.open");
    }

    public void close() {
        System.out.println("DefaultDocument.close");
    }

    public void save() {
        System.out.println("DefaultDocument.save");
    }

    public void revert() {
        System.out.println("DefaultDocument.revert");
    }
}
public class Main {
    public static void main(String[] args) {
        //템플릿 클래스를 이용하여 클래스 계통의 부피가 확장되는 문제 해결
        Application myApplication = new StandardApplication<MyDocument>(MyDocument.class);
        Document document = myApplication.createDocument();
        document.open();
        document.close();

        myApplication = new StandardApplication<YourDocument>(YourDocument.class);
        document = myApplication.createDocument();
        document.open();
        document.close();

        //Document를 상속받은 제품만 생성가능. 컴파일 시 에러 검출이 가능하다.
        //myApplication = new StandardApplication<DefaultDocument>(DefaultDocument.class); 컴파일 에러 검출 ㅇ
        //Raw Type을 사용하면 컴파일 시점에 에러검출이 되지않고 런타임에 발생할 수 있으니 사용을 자제해야한다.
        //myApplication = new StandardApplication(DefaultDocument.class); 컴파일 에러 검출 X
    }
}

 

관련 패턴

  • 추상 팩토리 패턴은 이 팩토리 메서드를 이용해서 구현할 때가 많다.
  • 팩토리 메서드는 템플릿 메서드 패턴에서도 사용될 때가 많다.

 


[참고자료]

리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)

https://stackoverflow.com/questions/61214535/whats-the-difference-between-factory-method-implementations

https://stackoverflow.com/questions/46114484/creator-in-factory-method-pattern

https://stackoverflow.com/questions/47480844/factory-method-pattern-vs-ordinary-abstract-class-implementation

https://stackoverflow.com/questions/63822775/applicability-for-the-factory-method-pattern

https://blog.asamaru.net/2016/04/11/create-instance-of-generic-type-in-java/

 

반응형

+ Recent posts