C++ 팩토리 패턴 심층 분석: 기초편

팩토리 패턴 소개

팩토리 패턴은 생성형 디자인 패턴의 한 종류로, 객체 생성의 최상의 방법을 제공합니다. 팩토리 패턴에서는 클라이언트에게 객체 생성 로직을 노출하지 않고, 공통 인터페이스를 사용하여 새로 생성된 객체를 가리킵니다.

간단히 말해, C++의 다형성 특성을 활용하여 상속 관계에 있는 클래스들을 팩토리 클래스를 통해 해당 서브 클래스(파생 클래스) 객체를 생성합니다. 복잡한 프로젝트에서는 서브 클래스 객체 생성을 용이하게 만들 수 있습니다.

팩토리 패턴의 구현 방식은 단순 팩토리 패턴, 팩토리 메소드 패턴, 추상 팩토리 패턴으로 나뉘며, 각 구현 방식마다 장단점이 존재합니다.

최근 신발 거래가 뜨거워지면서, 신발 공장의 형태로 각 구현 방식을 분석해 보겠습니다.

단순 팩토리 패턴

구체적인 상황:

- 신발 공장은 나이키, 아디다스, 리닝 브랜드의 신발을 생산할 수 있습니다. 어떤 신발이 인기가 많으면 사장님이 그 신발을 생산하며, 상황에 따라 생산합니다.

단순 팩토리 패턴의 구성 요소:

  1. 팩토리 클래스(`SimpleFactory`): 팩토리 패턴의 핵심 클래스로, 지정된 구체적인 인스턴스 객체를 생성하기 위한 인터페이스를 정의합니다.
  2. 추상 제품 클래스(`Product`): 구체적인 제품 클래스가 상속받는 부모 클래스 또는 구현하는 인터페이스입니다.
  3. 구체적인 제품 클래스(`NikeProduct\AdidasProduct\LiNingProduct`): 팩토리 클래스가 생성하는 객체가 바로 이 구체적인 제품 인스턴스입니다.

단순 팩토리 패턴의 특징:

- 팩토리 클래스가 구체적인 제품 객체를 생성하는 함수를 캡슐화합니다.

단순 팩토리 패턴의 단점:

- 확장성이 매우 떨어집니다. 새로운 제품을 추가할 때 팩토리 클래스를 수정해야 합니다.

단순 팩토리 패턴 코드 예제:

// 제품 추상 클래스
class Product
{
public:
    virtual ~Product() {}
    virtual void Display() = 0;
};

// 나이키 제품
class NikeProduct : public Product
{
public:
    void Display()
    {
        std::cout << "나는 나이키 제품입니다. 슬로건: Just do it" << std::endl;
    }
};

// 아디다스 제품
class AdidasProduct : public Product
{
public:
    void Display()
    {
        std::cout << "나는 아디다스 제품입니다. 슬로건: Impossible is nothing" << std::endl;
    }
};

// 리닝 제품
class LiNingProduct : public Product
{
public:
    void Display()
    {
        std::cout << "나는 리닝 제품입니다. 슬로건: Everything is possible" << std::endl;
    }
};
enum ProductType
{
    NIKE,
    LINING,
    ADIDAS
};

// 총 공장
class SimpleFactory
{
public:
    // 제품 유형에 따라 해당 제품 객체 생성
    Product *CreateProduct(ProductType type)
    {
        switch (type)
        {
        case NIKE:
            return new NikeProduct();
        case LINING:
            return new LiNingProduct();
        case ADIDAS:
            return new AdidasProduct();
        default:
            return nullptr;
        }
    }
};
int main()
{
    // 팩토리 객체 생성
    SimpleFactory factory;

    // 공장 객체로 나이키 제품 객체 생성
    Product *pNike = factory.CreateProduct(NIKE);
    if (pNike)
    {
        // 나이키 제품 광고 출력
        pNike->Display();
        // 리소스 해제
        delete pNike;
    }

    // 공장 객체로 리닝 제품 객체 생성
    Product *pLiNing = factory.CreateProduct(LINING);
    if (pLiNing)
    {
        // 리닝 제품 광고 출력
        pLiNing->Display();
        // 리소스 해제
        delete pLiNing;
    }

    // 공장 객체로 아디다스 제품 객체 생성
    Product *pAdidas = factory.CreateProduct(ADIDAS);
    if (pAdidas)
    {
        // 아디다스 제품 광고 출력
        pAdidas->Display();
        // 리소스 해제
        delete pAdidas;
    }

    return 0;
}

팩토리 메소드 패턴

구체적인 상황:

- 각종 신발 거래가 매우 활발해지면서, 각 브랜드의 신발을 대량 생산하기 위해 각 브랜드별로 독립적인 생산 라인을 개설해야 합니다. 이때 각 생산 라인은 동일한 브랜드의 신발만 생산할 수 있습니다.

팩토리 메소드 패턴의 구성 요소:

  1. 추상 팩토리 클래스(`Creator`): 팩토리 메소드 패턴의 핵심 클래스로, 구체적인 제품을 생성하기 위한 인터페이스를 제공하며, 이는 구체적인 팩토리 클래스에서 구현됩니다.
  2. 구체적인 팩토리 클래스(`NikeCreator\AdidasCreator\LiNingCreator`): 추상 팩토리를 상속받아, 해당 구체적인 제품 객체를 생성하는 방식을 구현합니다.
  3. 추상 제품 클래스(`Product`): 구체적인 제품이 상속받는 부모 클래스(기본 클래스)입니다.
  4. 구체적인 제품 클래스(`NikeProduct\AdidasProduct\LiNingProduct`): 구체적인 팩토리가 생성하는 객체가 바로 이 클래스입니다.

팩토리 메소드 패턴의 특징:

- 팩토리 메소드 패턴은 팩토리 클래스를 추상화하여 구체적인 제품을 생성하는 인터페이스를 제공하고, 이를 서브 클래스에 위임합니다.

- 팩토리 메소드 패턴의 적용은 구체적인 제품 객체의 생성을 캡슐화하는 것뿐만 아니라, 구체적인 제품 객체의 생성을 구체적인 팩토리 클래스에 구현하는 것을 목표로 합니다.

팩토리 메소드 패턴의 단점:

- 새로운 제품이 추가될 때마다 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 합니다. 단순 팩토리 패턴에 비해 더 많은 클래스 정의가 필요합니다.

- 하나의 생산 라인은 하나의 제품만 생산할 수 있습니다.

팩토리 메소드 패턴 코드 예제:

// 총 공장
class Creator
{
public:
    virtual Product *CreateProduct() = 0;
    virtual ~Creator() {}
};

// 나이키 생산자/생산 라인
class NikeCreator : public Creator
{
public:
    Product *CreateProduct() override
    {
        return new NikeProduct();
    }
};

// 아디다스 생산자/생산 라인
class AdidasCreator : public Creator
{
public:
    Product *CreateProduct() override
    {
        return new AdidasProduct();
    }
};

// 리닝 생산자/생산 라인
class LiNingCreator : public Creator
{
public:
    Product *CreateProduct() override
    {
        return new LiNingProduct();
    }
};
int main()
{
    // ================ 나이키 생산 프로세스 ==================== //
    // 공장에 나이키 생산 라인 개설
    Creator *nikeCreator = new NikeCreator();
    // 나이키 생산 라인에서 제품 생산
    Product *nikeProduct = nikeCreator->CreateProduct();
    // 나이키 제품 광고 출력
    nikeProduct->Display();
    // 리소스 해제
    delete nikeProduct;
    delete nikeCreator;

    // ================ 아디다스 생산 프로세스 ==================== //
    // 공장에 아디다스 생산 라인 개설
    Creator *adidasCreator = new AdidasCreator();
    // 아디다스 생산 라인에서 제품 생산
    Product *adidasProduct = adidasCreator->CreateProduct();
    // 아디다스 제품 광고 출력
    adidasProduct->Display();
    // 리소스 해제
    delete adidasProduct;
    delete adidasCreator;

    return 0;
}

추상 팩토리 패턴

구체적인 상황:

- 신발 공장은 사업을 확장하여 신발뿐만 아니라 운동 브랜드의 옷도 함께 생산하기로 결정했습니다.

추상 팩토리 패턴의 구성 요소:

  1. 추상 팩토리 클래스(`AbstractFactory`): 팩토리 메소드 패턴의 핵심 클래스로, 구체적인 제품을 생성하기 위한 인터페이스를 제공하며, 이는 구체적인 팩토리 클래스에서 구현됩니다.
  2. 구체적인 팩토리 클래스(`NikeFactory`): 추상 팩토리를 상속받아, 해당 구체적인 제품 객체를 생성하는 방식을 구현합니다.
  3. 추상 제품 클래스(`Footwear\Garment`): 구체적인 제품이 상속받는 부모 클래스(기본 클래스)입니다.
  4. 구체적인 제품 클래스(`NikeFootwear\NikeGarment`): 구체적인 팩토리가 생성하는 객체가 바로 이 클래스입니다.

추상 팩토리 패턴의 특징:

- 여러 제품군에서 제품 객체를 생성할 수 있는 인터페이스를 제공합니다. 예를 들어, 나이키 공장을 생성하면 나이키 신발 제품, 옷 제품, 바지 제품 등을 생성할 수 있습니다.

추상 팩토리 패턴의 단점:

- 팩토리 메소드 패턴과 마찬가지로, 새로운 제품이 추가될 때마다 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 합니다.

추상 팩토리 패턴 코드 예제:

// 기본 클래스: 의류
class Garment
{
public:
    virtual void Display() = 0;
    virtual ~Garment() {}
};

// 나이키 의류
class NikeGarment : public Garment
{
public:
    void Display() override
    {
        std::cout << "나는 나이키 의류입니다. 패션에 최고예요!" << std::endl;
    }
};

// 기본 클래스: 신발
class Footwear
{
public:
    virtual void Display() = 0;
    virtual ~Footwear() {}
};

// 나이키 신발
class NikeFootwear : public Footwear
{
public:
    void Display() override
    {
        std::cout << "나는 나이키 신발입니다. 멋지게 해드릴게요!" << std::endl;
    }
};
// 총 공장
class AbstractFactory
{
public:
    virtual Footwear *CreateFootwear() = 0;
    virtual Garment *CreateGarment() = 0;
    virtual ~AbstractFactory() {}
};

// 나이키 생산자/생산 라인
class NikeFactory : public AbstractFactory
{
public:
    Footwear *CreateFootwear() override
    {
        return new NikeFootwear();
    }

    Garment *CreateGarment() override
    {
        return new NikeGarment();
    }
};
int main()
{
    // ================ 나이키 생산 프로세스 ==================== //
    // 공장에 나이키 생산 라인 개설
    AbstractFactory *nikeFactory = new NikeFactory();

    // 나이키 생산 라인에서 신발 생산
    Footwear *nikeShoes = nikeFactory->CreateFootwear();
    // 나이키 생산 라인에서 의류 생산
    Garment *nikeClothes = nikeFactory->CreateGarment();

    // 나이키 신발 광고 출력
    nikeShoes->Display();
    // 나이키 의류 광고 출력
    nikeClothes->Display();

    // 리소스 해제
    delete nikeShoes;
    delete nikeClothes;
    delete nikeFactory;

    return 0;
}

요약

위 세 가지 팩토리 패턴은 모두 새로운 제품을 추가할 때 일정한 단점을 가지고 있습니다.

  • 단순 팩토리 패턴은 공장 클래스를 수정해야 하므로 개방-폐쇄 원칙을 위반합니다.
  • 팩토리 메소드 패턴과 추상 팩토리 패턴은 새로운 제품을 추가할 때 해당 제품에 대한 구체적인 팩토리 클래스를 추가해야 하므로 코드 작성량이 증가합니다.

그렇다면 새로운 제품을 추가할 때 공장 클래스를 수정하지 않고, 구체적인 팩토리 클래스도 추가하지 않는 좋은 방법은 없을까요?

실제 프로젝트에서 확장성이 매우 뛰어난 팩토리 클래스를 보았으며, 새로운 제품을 확장할 때 공장 클래스를 수정하거나 구체적인 팩토리 클래스를 추가할 필요가 없었습니다. 자세한 내용은 'C++ 팩토리 패턴 심층 분석: 고급편'으로 이동하여 읽어보시기 바랍니다.

태그: C++ 디자인패턴 팩토리패턴 단순팩토리 팩토리메소드

5월 25일 13:27에 게시됨