본문 바로가기

Design Patterns/영문 위키(Wikipedia)

Abstract Factory Pattern(추상 팩토리 패턴)

먼저 이 글은 영문 위키의 글을 번역한 것임을 알려드립니다.

영어 실력이 부족해 오역이 있을 수도 있으니 이해바랍니다.

원문 : http://en.wikipedia.org/wiki/Abstract_factory_pattern



생성 패턴 - Abstract Factory Pattern



The abstract factory pattern(이하 추상 팩토리 패턴)은 공통의 테마를 가진 팩토리의 그룹을 캡슐화하는 방법을 제공하는 소프트웨어 디자인 패턴이다. 일반적으로 클라이언트 소프트웨어는 추상 팩토리의 구체적인 구현체를 생성하고 그 구현체의 인터페이스를 사용한다. 클라이언트는 생성된 객체의 인터페이스만 사용하기 때문에 각각의 내부 팩토리로부터 얻는 구체적인 객체에 대해 알지 못한다. 이 패턴은 어던 객체들의 셋(또는 그룹)에 대한 구체적인 구현을 일반적인 사용법(인터페이스)로부터 분리시킨다. 


이에 대한 한 예를 들자. 

많은 종류의 제품(product)를 생성하는 인터페이스를 제공하는 DocumentCreator라는 추상 팩토리 클래스를 생각해볼 수 있다.(이 클래스는 createLetter()와 createResume()메서드를 이용해서 각각 편지와 이력서를 생성하도록 할 것이다.)

시스템은 DocumentCreator클래스를 상속한 몇몇 클래스를 가질 수 있다. 에를 들면 FancyDocumentCreator나 ModernDocumentCreator같은 클래스를 생각해 볼 수 있는데, 이 각각의 서브클래스는 createLetter()와 createResume()메서드를 각 클래스의 테마에 맞게 각각 다른 객체를 생성하도록할 수 있다. 

이렇게 생성되는 제품(여기에서 제품은 createLetter() 메서드로 생성되는 객체와 createResume() 메서드로 생성되는 객체를 의미한다.)은 클라이언트가 알고 있는 추상 클래스인 Letter와 Resume에서 유도된(derived, 서브클래스) 클래스이다.

클라이언트 코드에서는 DocumentCreator의 적절한 인스턴스(객체, 서브클래스)를 얻을 것이고 팩토리 메서드를 호출할 것이다. 각각의 결과 객체(각각의 결과 객체라 함은 Letter와 Resume라는 생성된 객체를 의미한다.)는 같은 DocumentCreator의 구현체로부터 만들어질 것이고, 같은 테마를 공유할 것이다. (추가 설명 : 클라이언트 코드에서는 FancyDocumentCreator나 ModernDocumentCreator 둘 중 하나의 추상 팩토리를 사용할 것이고, 둘 중 결정된 추상 팩토리에 따라 테마가 결정될 것이다. 즉, fancy테마인지 modern테마인지가 추상 팩토리를 결정하는 단계에서 결정이 되고, 클라이언트 코드에서 createLetter(), createResume()를 이용해서 생성한 객체는 같은 테마일 수 밖에 없을 것이다. 다시 말하면 FancyResume 객체와 ModernLetter를 같이 사용하는 일을 없다는 의미이다.) 클라이언트는 추상클래스인 Letter나 Resume에 대해서만 알면되고, 각각의 구체적인 클래스가 무엇인지에 대해서는 알 필요가 없다. 왜냐하면 그 구체적인 부분은 서브클래스 팩토리에서 결정하면 되는 부분이기 때문이다. 


팩토리는 어떤 객체들을 생성할 지 결정하는 위치(구체적인 클래스)이다. 이 패턴을 사용하는 목적은 객체의 생성을 사용하는 곳으로부터 분리시키는 것이다. 이는 베이스 클래스를 사용하는 코드(클라이언트 코드)에서 변경없이 새로운 타입의 서브클래스를 사용하도록 한다.


이 패턴을 사용하면 클라이언트 코드의 변경없이(심지어는 실행중에도) 구체 클래스를 교환하는 것이 가능하다는 것이다. 하지만 이패턴을 사용하면 비슷한 다른 패턴과 마찬가지로 불필요한 복잡도를 증가시킬 수 있고, 코드의 초기 작성에서 추가적인 작업이 들어갈 수 있다. 


정의 Definition

추상 팩토리 매서드 패턴의 정수(essence)는 "관련된 객체들의 집단(군, family)를 생성하는 인터페이스를 제공하되, 생성되는 객체의 구체적인 클래스를 알 필요없다"는 것이다.


사용법 Usage

팩토리는 생성될 객체의 구체적인 타입을 결정하고, 또한 구체적인 클래스가 생성되는 곳이다.

하지만, 팩토리는 생성되는 구체적인 객체의 추상 포인터만 반환한다.

이는 클라이언트가 팩토리 객체에게 생성할 객체의 추상 포인터를 리턴하도록 요청하기 때문에, 클라이언트 코드와 객체 생성을 분리시킨다. 

팩토리는 오직 추상 포인터만 반환을 하기 때문에, 클라이언트 코드는 실제 생성되는 객체의 구체적인 타입을 알 필요가 없다. 하지만, 구체적인 객체의 타입은 추상 팩토리가 알고 있다. (예를들면 팩토리가 설정 파일에서 이에 대한 정보를 읽을 수도 있을 것이다.) 설정 파일에서 이미 타입을 구체적으로 표현했기 때문에, 클라이언트는 타입을 구체화할 필요가 없다. 

이는 아래 내용을 의미한다.

  • 클라이언트 코드는 생성하려는 객체의 구체적인 타입이 무엇이든간에 알 필요가 없다. (구체적인 타입과 관련된 헤더 파일이나 클래스 정의를 포함할 필요가 없다.) 클라이언트 코드는 오직 추상적인 타입만을 다룰 뿐이다. 구체적인 타입의 객체는 팩토리에 의해서 생성되고, 클라이언트 코드에서는 추상 인터페이스를 통해서만 그러한 객체에 접근을 하는 것이다. 
  • 새로운 구체적인 타입을 추가하는 것은 클라이언트에서 다른 팩토리를 사용하도록 수정함으로써 해결된다. 이는 단지 1 줄의 코드 변경으로 될 것이다.(그렇게 하면 다른 팩토리는 다른 구체적인 타입의 객체를 생성하지만, 여전히 같은 추상 클래스 의 포인터를 반환한다.) 이는 새로운 타입의 객체를 생성하기 위해 클라이언트 코드를 수정하는 것보다(추상 팩토리를 사용하지 않으면 새로운 객체를 생성하는 클라이언트 코드의 모든 곳을 변경해야만 한다.) 훨씬 쉬운 방법이다. 뿐만 아니라 추상 팩토리를 사용하지 않으면 새로운 객체를 생성하는 모든 코드에서 구체적인 클래스에 대한 헤더 파일을 포함시킴으로써 구체적인 클래스 타입을 알아야만 한다. 만약 모든 팩토리 객체가 하나의 전역 싱글톤(singleton)객체에 저장되어 있고, 모든 클라이언트 코드가 객체 생성을 위한 적절한 팩토리에 접근하기 위해 싱글톤을 이용한다면, 팩토리를 변경시키는 것은 싱글톤 객체를 변화시키는 것만큼 쉬운 일이다. 



구조 Structure


GuiFactory인터페이스의 createButton()메서드는 Button타입의 객체를 반환한다. 어떤 Button의 구체적인 타입이 반환되는지는 GuiFactory의 어떤 서브 클래스를 사용하는지에 따라 달려있다.

위의 예는 간결성을 위해서 한 객체의 생성에 관해서만 보여주고 있다. 

(실제 예에서는 createButton()뿐만 아니라, createLabel(), createImageView(), createPane() 등의 메서드가 있을 수 있다.)


예 Example

어떤 타입의 팩토리를 사용하냐에 따라 출력물은 "I'm a WinButton"이나 "I'm an OSXButton"이 될 것이다. 이 애플리케이션은 어떤 종류의 GUIFactory가 사용되는지, 또 어떤 타입의 Button이 사용되는지 알지 못한다는 점에 주목하자.


Java


/* GUIFactory example -- */
 
interface GUIFactory {
    public Button createButton();
}
 
class WinFactory implements GUIFactory {
    public Button createButton() {
        return new WinButton();
    }
}
class OSXFactory implements GUIFactory {
    public Button createButton() {
        return new OSXButton();
    }
}
 
interface Button {
    public void paint();
}
 
class WinButton implements Button {
    public void paint() {
        System.out.println("I'm a WinButton");
    }
}
class OSXButton implements Button {
    public void paint() {
        System.out.println("I'm an OSXButton");
    }
}
 
class Application {
    public Application(GUIFactory factory) {
        Button button = factory.createButton();
        button.paint();
    }
}
 
public class ApplicationRunner {
    public static void main(String[] args) {
        new Application(createOsSpecificFactory());
    }
 
    public static GUIFactory createOsSpecificFactory() {
        int sys = readFromConfigFile("OS_TYPE");
        if (sys == 0) {
            return new WinFactory();
        } else {
            return new OSXFactory();
        }
    }
}