팩토리 메소드 패턴

GoF의 디자인패턴 중 객체생성의 팩토리 메소드 패턴을 Java 로 정리한 글

다양한 구현체 (Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.

1. 정의(Definition)

1-1. 팩토리 패턴?

객체의 생성을 캡슐화 하는 패턴이다.

구체적인 객체의 생성 과정을 팩토리모듈화 하여 구체적인 부분이 아닌 추상적인 부분에 의존할 수 있도록 한다.

팩토리 패턴에는 팩토리 메소드 패턴추상 팩토리 패턴이 있다.

Pattern차이점

팩토리 메소드 패턴

상속을 통해 서브 클래스에서 팩토리 메소드를 오버라이딩하여 객체의 생성부를 구현

추상 팩토리 패턴

객체의 집합을 생성하기 위한 정의를 추상체에 위치시키고 하위의 구현체에서 세부적인 집합 생성 과정을 구현 (Fatory Method를 이용해 구현)

공통점 : 객체의 생성부를 캡슐화하여 결합을 느슨하게 함 구체적인 타입에 의존하지 않도록

먼저 팩토리 메소드 패턴을 살펴보자.

1-2. 팩토리 메소드 패턴?

부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다.

2. 사용 이유

객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스에서 이루어지도록 하여 재정의 가능한 것으로 설계 하지만, 복잡해지지 않게 한다.

  • 생성할 객체 타입을 예측할 수 없을 때

  • 생성할 객체를 기술하는 책임을 서브클래스에게 정의하고자 할 때

  • 객체 생성의 책임을 서브클래스에 위임시키고 서브클래스에 대한 정보를 은닉하고자 할 때

3. 장점과 단점

3-1. 장점

  • 기존 코드(인스턴스를 만드는 과정)를 수정하지 않고 새로운 인스턴스를 다른 방법으로 생성하도록 확장할 수 있다.

    • Product 와 Creator 간의 커플링(결합)이 느슨함

    • 확장에 열려있고 변경에 닫혀있는 객체지향 원칙을 적용했기 때문에 가능

      • 확장 : 새로운 인스턴스 추가

      • 변경 : 기존 코드를 수정

  • 코드가 간결해진다.

  • 병렬적 클래스 계층도를 연결하는 역할을 담당할 수 있음

✍🏻 팩토리 메소드 패턴을 적용할 때 참고

  1. 자바 8의 인터페이스 default 메소드

  2. 인터페이스에 추상 메소드가 아닌 default 메소드를 통해 기능을 구현할 수 있게 되어서 상속받는 서브클래스의 중복코드를 제거할 수 있다.

  3. 자바 9의 인터페이스의 private 메소드

  4. 기능 구현이 가능해지면서 인터페이스의 내부 로직을 private 메소드로 구현하면 읽기 좋은 코드로 작성할 수 있게 된다.

1-3-2. 단점

  • 클래스가 많아진다. (클래스 계층도 커질 수 있다.)

    • 제품 클래스가 바뀔 때마다 새로운 서브클래스를 생성해야 한다.

  • 클라이언트가 creator 클래스를 반드시 상속해 Product를 생성해야 한다.

4. 팩토리 메소드 패턴 예제

public class Client {

    public static void main(String[] args) {
        Client client = new Client();
        client.print(new APizzaFactory());
        client.print(new BPizzaFactory());
    }

    private void print(PizzaFactory pizzaFactory) {
        pizzaFactory.requestOrder();
    }

}

public interface PizzaFactory {
    default void requestOrder() {
        prepare();
        Pizza pizza = createPizza();
        pizza.makePizza();
        delivery(pizza.getPizza());
        complete();
    }
    default void prepare(){
        System.out.println("피자 만들 준비중 ");
    }
    default void delivery(String pizza) {
        System.out.println(pizza + " 배달중");
    }
    default void complete() {
        System.out.println("피자 배달 완료");
    }

    Pizza createPizza();    //서로 다른 클래스
}

public class APizzaFactory implements PizzaFactory {
    @Override
    public Pizza createPizza() {
        return new APizza();
    }
}

public class BPizzaFactory implements PizzaFactory{
    @Override
    public Pizza createPizza() {
        return new BPizza();
    }
}

public interface Pizza {
    void makePizza();
    String getPizza();
}

@Getter
public class APizza implements Pizza{
    private String name;
    @Override
    public void makePizza() {
        System.out.println("A피자 만드는중");
        name = "A피자";
    }

    @Override
    public String getPizza() {
        return name;
    }
}

@Getter
public class BPizza implements Pizza{
    private String name;
    @Override
    public void makePizza() {
        System.out.println("B피자 만드는중");
        name = "B피자";
    }

    @Override
    public String getPizza() {
        return name;
    }
}

다이어그램

5. 실제 사용되는

실제 자바코드에 어떻게 적용되어있는지 예시를 살펴보자.

5-1. 단순한 팩토리 패턴

매개변수의 값에 따라 또는 메소드에 따라 각기 다른 인스턴스를 리턴하는 단순한 버전 의 팩토리 패턴

java.util.Calendar#getInstance()

public class CalendarExample {
    
    public static void main(String[] args) {
    	log.info("Calendar 예시 : {}", Calendar.getInstance());
    }
}
  • getInstance() 를 호출할 때마다 새로운 Calendar 객체가 생성된다.

  • Calendar는 Gregorian형식(우리가 현재 쓰는), Julian 형식이 있는데, 이 두가지 경우를 모두 커버하기 위해 팩토리 메소드 패턴으로 디자인 되었다.

싱글톤(singletone)패턴에서 사용되는 이름 같은데, naming이 약간 잘못 지어진 것 같다

java.text.NumberFormat#getInstance()

public class NumberFormatExample {
    
    public static void main(String[] args) {
    	log.info("일반적인 NumberFormat 예시 : {}", NumberFormat.getInstance());
    	log.info("정수형 객체 생성 NumberFormat 예시 : {}", NumberFormat.getIntegerInstance());
    	log.info("통화수치 객체 생성 NumberFormat 예시 : {}", NumberFormat.getCurrencyInstance());
    }
}
  • 국가에 따라 또는 화폐에 따라 다른 표현 방식을 커버하기 위해 팩토리 메소드 패턴으로 디자인 되었다.

  • NumberFormat을 구현하는 클래스는 DecimalFormat, ExponentialFormat 등이 있다.

5-2. 스프링 BeanFactory

Object 타입의 Product를 만드는 BeanFacotroy라는 Creator

BeanFactory xmlFactory = new ClassPathXmlApplicationContext("config.xml");
String hello = xmlFactory.getBean("hello", String.class);

BeanFactory javaFactory = new AnnotationConfigApplicationContext(Config.class);
String hi = javaFactory.getBean("hi", String.class);

스프링이 제공하는 BeanFactory가 Creator 인터페이스이고, Creator를 구현한 ConcreateCreator가 위 2개의 구현체가 가장 흔히 사용된다.

  • xml 설정과 java 설정으로 읽어오는 방식이다.

  • Product에 해당하는 것은 Object

  • ConcreteProduct에 해당하는 것은 xml 또는 class Bean

6. 결론

팩토리 메소드 패턴을 사용하는 이유는 클래스간의 결합도를 낮추기 위한 것 같다. 결합도라는 것은 간단히 말해 클래스의 변경할 부분이 생겼을 때 얼마나 다른 클래스에도 영향을 주는가이다. 팩토리 메소드 패턴을 사용하는 경우 직접 객체를 생성해 사용하는 것을 방지하고 서브 클래스에 위임함으로써 보다 효율적인 코드 제어를 할 수 있고 의존성을 제거한다. 결과적으로 결합도 또한 낮출 수 있다.

Reference

Last updated