코딩으로 학습하는 GoF의 디자인 패턴 - 팩토리 메소드 패턴

팩토리 메소드 패턴

alt text

팩토리 메소드 패턴 : 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.

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

  • Creator : 최상위 공장 클래스로서, 팩토리 메서드를 추상화하여 서브 클래스로 하여금 구현하도로 함
    • 객체 생성 처리 메서드(someOperartion) : 객체 생성에 관한 전처리, 후처리를 템플릿화한 메소드
    • 팩토리 메서드(createProduct) : 서브 공장 클래스에서 재정의할 객체 생성 추상 메서드
  • ConcreteCreator : 각 서브 공장 클래스들은 이에 맞는 제품 객체를 반환하도록 생성 추상 메소드를 재정의한다. 즉, 제품 객체 하나당 그에 걸맞는 생산 공장 객체가 위치
  • Product : 제품 구현체를 추상화
  • ConcreteProduct : 제품 구현체

팩토리 메소드 (Factory method) 패턴 구현 예시

확장에 열려있고 변경에 닫혀있는 구조로 만들어보자. 크게 제품 계층과 크리에이터 계층으로 나뉘어 있다.

제품 계층

Ship

public class Ship {

    private String name;

    private String color;

    private String logo;

    private Wheel wheel;

    private Anchor anchor;

    // ~~~

}

BlackShip

public class Blackship extends Ship {

    public Blackship() {
        setName("blackship");
        setColor("black");
        setLogo("⚓");
    }
}

Whiteship

public class Whiteship extends Ship {

    public Whiteship() {
        setName("whiteship");
        setLogo("\uD83D\uDEE5️");
        setColor("white");
    }
}
크리에이터(팩토리) 계층

ShipFactory

public interface ShipFactory {

    default Ship orderShip(String name, String email) {
        validate(name, email);
        prepareFor(name);
        Ship ship = createShip();
        sendEmailTo(email, ship);
        return ship;
    }

    void sendEmailTo(String email, Ship ship);

    Ship createShip();

    private void validate(String name, String email) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("배 이름을 지어주세요.");
        }
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("연락처를 남겨주세요.");
        }
    }

    private void prepareFor(String name) {
        System.out.println(name + " 만들 준비 중");
    }

}

DefaultShipFactory

public abstract class DefaultShipFactory implements ShipFactory {

    @Override
    public void sendEmailTo(String email, Ship ship) {
        System.out.println(ship.getName() + " 다 만들었습니다.");
    }

}

BlackshipFactory

public class BlackshipFactory extends DefaultShipFactory {
    @Override
    public Ship createShip() {
        return new Blackship();
    }
}

WhiteshipFactory

public class WhiteshipFactory extends DefaultShipFactory {
    @Override
    public Ship createShip() {
        return new Whiteship();
    }
}

팩토리 메소드 패턴 사용 시기

  • 클래스 생성과 사용의 처리 로직을 분리하여 결합도를 낮추고자 할 때
  • 코드가 동작해야 하는 객체의 유형과 종속성을 캡슐화를 통해 정보 은닉 처리 할 경우
  • 라이브러리 혹은 프레임워크 사용자에게 구성 요소를 확장하는 방법을 제공하려는 경우
  • 기존 객체를 재구성하는 대신 기존 객체를 재사용하여 리소스를 절약하고자 하는 경우
    • 상황에 따라 적절한 객체를 생성하는 코드는 자주 중복될 수 있다. 그리고 객체 생성 방식의 변화는 해당되는 모든 코드 부분을 변경해야 하는 문제가 발생한다.
    • 따라서 객체의 생성 코드를 별도의 클래스 / 메서드로 분리 함으로써 객체 생성의 변화에 대해 대비를 하기 위해 팩토리 메서드 패턴을 이용한다고 보면 된다.
    • 특정 기능의 구현은 별개의 클래스로 제공되는 것이 바람직한 설계이기 때

장단점

장점

어떤 인스턴스를 만드는 그런 과정이 담겨있는 기존 로직을 건드리지 않고 그와 같은 류의 새로운 인스턴스르 다른방법으로 얼마든지 확장이 가능하다는게 장점이 되고, 이렇게 가능하지게 된 이유는 그 프로덕트와 크리에이터 간의 커플링을 루즐리하게 가져갔기 떄문이다.

크리에이터와 그 인스턴스에 해당하는 프로덕트 간의 관계를 느슨하게 가져갔기 때문에 확장에 열려있고 변경에 닫혀있는 OCP 원칙을 따를 수 있게 된다.

단점

각자의 역할을 나누다 보니 팩토리 크리에어터와 제품의 계층 구조를 구현하기 위해 클래스가 늘어난다는 단점이 있다.

자바 8에 추가된 default 메소드

인터페이스에는 항상 추상 메소드만 정의를 할 수가 있었고 그 인터페이스를 구현하는 클래스에서 그 메소드를 구현하거나 아니면 그 인터페이스를 구현하는 클래스가 추상 클래스로 만들어지거나 그러한 방법밖에 없었다.

하지만 Java 8부터는 인터페이스에 기본 구현체를 만들수가 있어서 그 인터페이스를 구현하는 클래스 또는 그 인터페이스를 상속받은 또 다른 인터페이스 해당하는 기능을 사용할 수 있겠금 되었다 그래서 Java8부터는 추상클래스를 그렇게 많이 쓰지는 않는다.

실무에서 쓰이는 팩토리 메서드 예시

스프링 BeanFactory

// 컴포넌트 스캔, bean 설정 어노테이션, xml 파일 등의 bean 들을 매개변수로 주어 ConcreateProduct 를 반환
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);
  • Object 타입의 Product 를 만드는 BeanFactory 라는 Creator(최상위 공장 인터페이스)역할
  • BeanFactory 가 Creator 인터페이스이고 이를 구현한 ClassPathXmlApplicationContext, AnnotationConfigApplicationContext 가 ConcreteCreator(공장 구현체) 역할
  • 여기서 넘겨주는 Product 는 Object 타입이고 이를 넘겨 받는 인스턴스가 ConcreateProduct(제품) 가 된다.
  • 컴포넌트 스캔, bean 설정 어노테이션, xml 파일 등의  bean 들이 결국 ConcreateProduct 에 해당되는 것
  • ioc 컨테이너에 해당되는 클래스가 bean 팩토리 인데 이부분에 녹아져 있는 패턴이 팩토리 메서드 패턴