Factory Method

생성 패턴의 한 종류, 가상 생성자 (Virtual Constructor)라고도 한다.

사용 의도

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

구체적 상황

문제 상황

물류 관리 앱을 만들고 있다고 가정하자. 첫 번째 버전은 트럭을 이용해 운송을 다룰 수 있는 버전이다. 그래서 지금 많은 양의 코드가 Truck이라는 클래스에 들어있다.

이후, 해양 운송 회사들이 해양 운송 버전도 만들어달라고 부탁한 상황이다. 이런 상황에서 대부분의 트럭에 있는 코드들이 Ship이라는 코드에 붙을 때 많은 변경 사항이 필요하다. 그리고 만약 다른 버전의 운송을 더해야 할 경우에 문제는 더 심해질 것이다.

해결 방법

팩토리 메서드는 직접적인 객체 구성을 factory 메서드로 대체한다(Truck, Ship 대신에). 여전히 new를 사용하지만, factory 메서드에 의해 호출된다. factory 메서드에 의해 반환되는 객체들은 products로 불린다.

factory1.PNG

생성자 호출을 다른 위치로 옮긴 것 뿐이지만, 이제 팩토리 메서드를 subclass에서 오버라이드 할 수 있다.
다만, 작은 제한이 있는데, subclass들은 공통적인 base 클래스, 또는 인터페이스를 가지고 있어야만 (상속 받았어야만) 다른 종류의 타입 product를 리턴할 것이다. 그리고 base 클래스 안의 factory 메서드는리턴 타입이 서브 클래스가 상속받은 인터페이스로서 선언되어 있어야 한다.

factory2.PNG

예를 들어서 TructShip 클래스들은 deliver이라는 메서드가 선언된 Transport 인터페이스를 실행해야 한다.

클라이언트 코드는 실제 상품별로 다른 차이점을 갖지 않고, 모든 상품들을 추상적인 Transport 인터페이스를 이용해서 다룬다. 클라이언트들은 모든 운송 방식이 deliver 메서드를 갖는다는 것을 알고 있고, 내부가 어떻게 굴러가는지는 상관할 필요가 없다.

참여자

  • Product: Factory 메서드가 생성하는 객체의 인터페이스를 정의

  • ConcreteProduct: Product 클래스에 정의된 인터페이스를 실제로 구현한다.

  • Creator: Product 타입의 객체를 반환하는 팩토리 메서드를 선언한다. Creator 클래스는 팩토리 메서드를 기본적으로 구현하는데, 이 구현에서는 ConcreteProduct 객체를 반환한다. 또한 Product 객체의 생성을 위해 팩토리 메서드를 호출한다.

  • ConcreteCreator: 팩토리 메서드를 재정의해서 ConcreteProduct의 인스턴스를 반환한다.

    협력 방법

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

    협력 결과

    :)

  • Creator와 ConcreteProduct 사이의 긴밀한 종속 관계를 피할 수 있다. 즉, Creator가 사용자가 정의한 어떠한 ConcreteProduct와도 동작할 수 있다.

  • 단일 책임 원칙을 실행할 수 있다. Product creation 코드를 Creator 클래스에서만 처리하게 한다.

  • 개방-폐쇄 원칙. 기존 클라이언트 코드를 깨지 않고 프로그램에 새로운 타입의 상품을 만들어 넣을 수 있다. (확장에 개방적, 수정에 폐쇄적)

    :(

  • 이 패턴을 수행하기 위해 보다 많은 subclass가 필요해져서 코드가 복잡해질 수 있다.

  • 사용자가 ConcreteProduct 객체 하나만 만들기 위해서도 Creator 클래스를 서브클래싱 해야할 수 있다.

    구현 방법

  1. 모든 Products 들이 같은 인터페이스를 갖도록 만든다.
  2. Creator 클래스에 빈 factory 메서드를 넣는다. 리턴 타입은 Products들의 공통된 인터페이스 타입이어야 한다.
  3. Creator 코드에서 모든 product를 생성할 수 있게 하나씩 분기한다. factory 메서드에서는 리턴되는 product 타입을 구분할 수 있도록 임시적인 파라미터가 들어가야 할 것이다. 지금까지 코드는 복잡한 switch 문으로 이루어져 있을 것인데, 곧 수정 될 것.
  4. 이제 factory 메서드 안에 나열된 product 타입별로 creator 서브클래스들을 만든다. 서브클래스에서 factory 메서드를 오버라이드하고, base method에서 해당 부분을 제외해라
    1. 모두 제외했다면, base factory 메서드는 비어있을 것이고, 추상 메서드로 바꿀 수 있다.

구현시 고려 사항

  • 구현 방법은 크게 두 가지
    1. Creator 클래스를 추상 클래스로 정의 (팩토리 메서드 내부를 구현하지 않음. 선언만)
    2. Creator 클래스를 구체 클래스로 만듦 (팩토리 메서드에 대한 기본 구현을 제공)

코드 (Typescript)

Code

abstract class Creator {
  public abstract factoryMethod(): TransportProduct;

  public operation(): string {
    const product = this.factoryMethod();
    return `Creator: Delivery Product is made: ${product.deliver()}`;
  }
}

class RoadLogistics extends Creator {
  public factoryMethod(): TransportProduct {
    return new Truck();
  }
}

class SeaLogistics extends Creator {
  public factoryMethod(): TransportProduct {
    return new Ship();
  }
}

interface TransportProduct {
  deliver(): string;
}

class Truck implements TransportProduct {
  public deliver(): string {
    return `Delivery with Truck`;
  }
}

class Ship implements TransportProduct {
  public deliver(): string {
    return `Delivery with Ship`;
  }
}

function clientCode(creator: Creator) {
  console.log("Client: Use Creator.");
  console.log(creator.operation());
}

console.log(`App: Launched with the RoadLogistics.`);
clientCode(new RoadLogistics());
console.log("");

console.log(`App: Launched with the SeaLogistics.`);
clientCode(new SeaLogistics());

Output

App: Launched with the RoadLogistics.
Client: Use Creator.
Creator: Delivery Product is made: Delivery with Truck

App: Launched with the SeaLogistics.
Client: Use Creator.
Creator: Delivery Product is made: Delivery with Ship

Ref