생성 패턴의 한 종류, 가상 생성자 (Virtual Constructor)라고도 한다.
물류 관리 앱을 만들고 있다고 가정하자. 첫 번째 버전은 트럭을 이용해 운송을 다룰 수 있는 버전이다. 그래서 지금 많은 양의 코드가 Truck
이라는 클래스에 들어있다.
이후, 해양 운송 회사들이 해양 운송 버전도 만들어달라고 부탁한 상황이다. 이런 상황에서 대부분의 트럭에 있는 코드들이 Ship
이라는 코드에 붙을 때 많은 변경 사항이 필요하다. 그리고 만약 다른 버전의 운송을 더해야 할 경우에 문제는 더 심해질 것이다.
팩토리 메서드는 직접적인 객체 구성을 factory
메서드로 대체한다(Truck
, Ship
대신에). 여전히 new
를 사용하지만, factory 메서드에 의해 호출된다. factory
메서드에 의해 반환되는 객체들은 products로 불린다.
생성자 호출을 다른 위치로 옮긴 것 뿐이지만, 이제 팩토리 메서드를 subclass에서 오버라이드 할 수 있다.
다만, 작은 제한이 있는데, subclass들은 공통적인 base 클래스, 또는 인터페이스를 가지고 있어야만 (상속 받았어야만) 다른 종류의 타입 product를 리턴할 것이다. 그리고 base 클래스 안의 factory 메서드는리턴 타입이 서브 클래스가 상속받은 인터페이스로서 선언되어 있어야 한다.
예를 들어서 Truct
과 Ship
클래스들은 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 클래스를 서브클래싱 해야할 수 있다.
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());
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