Typescript로 다시 쓰는 GoF - Factory Method

아홉번째태양·2023년 8월 9일
0

Factory Method란?

앞선 장에서 살펴봤던 Template Method를 인스턴스를 생성할 때 적용한 패턴이 Factory Method이다.

Factory Method에서는 인스턴스 생성 방법을 상위 클래스에서, 즉 Template Method를 통해 결정하되, 구체적으로 어떻게 생성할지는 하위 클래스에 맡긴다.



Factory Method 구현

Factory Method에는 다음의 네 가지 객체가 등장한다.

  1. Product
    Product는 Factory를 거쳐 생산되는 결과물, 즉 제품을 추상화한 객체다. 따라서, 해당 제품을 사용하기 위한 인터페이스의 형태가 이곳에서 정의된다.

  2. Creator
    Creator는 Template Method가 사용되는 객체로서 Product를 만들어내는 구체적인 메소드를 정의한다.

여기서 Creator는 어떤 구체적인 제품이 만들어질지 모른다. 따라서, 직접적으로 new 키워드를 사용해 인스턴스를 생성하는 대신 인스턴스를 생성하는 추상 메소드만 정의하여 어떤 인스턴스가 만들어질지는 하위 클래스에 위임한다.

  1. Concrete Product
    실제 Product에 해당하는 객체다. 따라서 실제로 사용자가 접근하고 사용하는 메소드들은 이곳에서 구현된다.

  2. Concrete Creator
    마찬가지로 실제 Creator 구현체에 해당하며, Creator의 Template Method를 완성하는 역할을 한다.


이를 바탕으로 사용자의 IDCard를 찍어내는 IDCardFactory를 구현해보자.


Product

Product에는 실제 사용자가 사용하게될 메소드를 미리 정의해야한다. 필요에 따라 코드를 미리 구현할 수도 있으며 경우에 맞게 interfaceabstract class를 적절하게 사용한다.

여기서는 use라는 사용 가능한 메소드를 선언하고 구체적인 구현은 하위 클래스에 맡긴다.

interface Product {
    use(): void;
}

Creator

Template Method에 해당하는 create 메소드를 구현하되, create의 로직을 담당하는 createProductregisterProduct는 추상 메소드로 남겨놔서 구체적으로 어떤 인스턴스를 생성할지는 하위 클래스에 맡긴다.

abstract class Factory {
    create(owner: string): Product {
        const product = this.createProduct(owner);
        this.registerProduct(product);
        return product;
    }

    protected abstract createProduct(owner: string): Product;
    protected abstract registerProduct(product: Product): void;
}

Concrete Product

이제 구체적인 Product, 즉 본 예시에서의 IDCard를 구체적으로 정의한다.

IDCard는 Product의 기본 메소드인 use도 있지만, IDCard 자체를 식별하거나 사용자를 나타낼 수 있는 메소드를 추가적으로 구현한다.

class IDCard implements Product {
    private owner: string;

    constructor(owner: string) {
        console.log(`${owner}의 카드를 만듭니다.`);
        this.owner = owner;
    }

    use(): void {
        console.log(`${this.owner}을 사용합니다.`);
    }

    toString(): string {
        return `[IDCard: "${this.owner}"]`;
    }

    getOwner(): string {
        return this.owner;
    }
}

Concrete Creator

마지막으로 IDCard를 찍어낼 IDCardFactory를 구현한다. 여기서는 Creator에서 추상메소드로 남겨둔 createProductregisterProduct를 실제로 구현하기만 한다.

class IDCardFactory extends Factory {
    private owners: string[] = [];

    protected createProduct(owner: string): IDCard {
        return new IDCard(owner);
    }

    protected registerProduct(product: IDCard): void {
        this.owners.push(product.getOwner());
        console.log(`${product.toString()}을 등록합니다.`)
    }

    getOwners(): string[] {
        return this.owners;
    }
}

동작 확인

이제 Factory를 이용해 여러개의 IDCard를 실제로 찍어내고 사용해보자.

(() => {
    const factory = new IDCardFactory();

    const card1 = factory.create('홍길동');
    const card2 = factory.create('이순신');
    const card3 = factory.create('강감찬');

    card1.use();
    card2.use();
    card3.use();
})();
홍길동의 카드를 만듭니다.
[IDCard: "홍길동"]을 등록합니다.
이순신의 카드를 만듭니다.
[IDCard: "이순신"]을 등록합니다.
강감찬의 카드를 만듭니다.
[IDCard: "강감찬"]을 등록합니다.
홍길동을 사용합니다.
이순신을 사용합니다.
강감찬을 사용합니다.


Factory Method 언제 쓸까?

위 예제에서 살펴본 객체들은 두 가지 그룹으로 나눌 수 있다.

  • framework - Product, Factory
  • service - IDCard, IDCardFactory

이 두 그룹은 하나의 커다란 특징을 가진다,
service에서는 framework를 가져와서 사용해야하지만, 반대로 framework는 service가 어떻게 생겼는지 전혀 상관이 없다. framework를 전혀 수정하지 않고도 완전히 다른 service, 즉 새로운 제품들과 그 제품을 찍어내는 공장을 만들 수 있는 것이다.

따라서 공용 라이브러리처럼 혹은 그와 유사한 상황에서 공통 기능을 구현하는 경우에 Factory Method는 유용하게 쓰일 수 있다.




참고자료

Java언어로 배우는 디자인 패턴 입문 - 쉽게 배우는 Gof의 23가지 디자인패턴 (영진닷컴)
Javascript MDN
Typescript Documentation

0개의 댓글