팩토리 패턴(Factory Pattern)

JP Kim·2024년 9월 23일

디자인 패턴

목록 보기
1/1

팩토리 패턴이란?

팩토리 패턴은 객체 생성 매커니즘을 별도의 클래스나 메서드로 분리하여, 객체생성을 담당하는 코드와 클라이언트 코드(객체를 사용하는 코드)를 분리하는 디자인 패턴입니다.
이 패턴을 통해 객체 생성 과정을 캡슐화 할 수 있으며 클라이언트 코드가 객체의 생성 방식에 의존하지 않게 되어 코드의 확장성과 유지보수성이 향상됩니다.

팩토리 패턴의 사용 이유

1. 객체 생성의 캡슐화

객체 생성 매커니즘이 별도 클래스나 메서드로 분리되기에 코드 구조가 깔끔해집니다.

2. 확장성

새로운 객체가 추가할 때 클라이언트 코드를 수정하지 않고, 객체 생성을 담당하는 팩토리 클래스나 메서드를 확장하거나 수정하기만 하면 됩니다.

3. 의존성 감소

클라이언트 코드가 구체적인 클래스에 의존하는 것이 아닌 추상 클래스나 인터페이스에 의존하기에 코드를 수정할 때 보다 용이해집니다.

팩토리 패턴의 종류

팩토리 패턴은 주로 심플 팩토리 패턴, 팩토리 메서드 패턴, 추상 팩토리 패턴이 있습니다.

심플 팩토리 패턴

심플 팩토리 패턴은 객체 생성을 담당하는 클래스나 메서드를 따로 두는 것을 의미합니다.

예시 코드 :

// Animal 인터페이스 정의
public interface Animal
{
    void Speak();
}

// Cat 클래스 정의
public class Cat : Animal
{
    public void Speak()
    {
        Console.WriteLine("Meow");
    }
}

// Dog 클래스 정의
public class Dog : Animal
{
    public void Speak()
    {
        Console.WriteLine("Woof");
    }
}

// AnimalFactory 클래스 정의 (팩토리 패턴)
public class AnimalFactory
{
    // 동물을 생성하는 메서드
    public static Animal CreateAnimal(string animalType)
    {
        if (animalType == "Cat")
        {
            return new Cat();
        }
        else if (animalType == "Dog")
        {
            return new Dog();
        }
        else
        {
            throw new ArgumentException("Unknown animal type");
        }
    }
}

// 사용 예시 (클라이언트 코드)
class Program
{
    static void Main(string[] args)
    {
        // 팩토리 패턴을 이용한 객체 생성
        Animal cat = AnimalFactory.CreateAnimal("Cat");
        Animal dog = AnimalFactory.CreateAnimal("Dog");

        cat.Speak(); // 출력: Meow
        dog.Speak(); // 출력: Woof
    }
}

위 코드를 살펴보면 AnimalFactory 클래스에서 if-else 문을 통해 클라이언트에서 사용할 동물 객체를 CreateAnimal() 메서드를 활용해 객체를 생성하고 있습니다.
이처럼 심플 팩토리는 생성 로직을 팩토리 클래스에 모아두는 것을 의미합니다.

다만 심플 팩토리 패턴은 만약 다른 클래스가 추가되었을 때 기존 코드를 수정해야합니다. 이는 확장에는 열려있으나 변경에는 닫혀있어야 하는 OCP 원칙(Open Close Principle)에 위배된다는 문제가 있습니다.

예를 들어, 또 다른 객체로 HorseMonkey가 추가 된다면 AnimalFactory 클래스를 수정해야만 합니다.

이는 프로젝트가 작을 때는 문제가 되지 않으나 프로젝트의 규모가 커지면서 문제가 발생할 수도 있습니다.

팩토리 메서드 패턴

팩토리 메서드 패턴은 객체 생성 방식을 하위 클래스에서 결정하는 패턴입니다. 즉 객체를 생성하는 매커니즘은 부모 클래스에서 정의하되, 실제로 생성되는 객체의 종류는 자식 클래스에서 결정됩니다.

  • 부모 클래스 (또는 인터페이스) : 객체 생성 메서드를 정의
  • 자식 클래스 : 실제 객체를 생성하는 로직을 구현

예시 코드 :

코드 복사
// Product 인터페이스 정의
public interface Product
{
    void Use();
}

// ConcreteProductA 클래스 정의
public class ConcreteProductA : Product
{
    public void Use()
    {
        Console.WriteLine("Using Product A");
    }
}

// ConcreteProductB 클래스 정의
public class ConcreteProductB : Product
{
    public void Use()
    {
        Console.WriteLine("Using Product B");
    }
}

// Creator 추상 클래스 (팩토리 메서드 패턴)
public abstract class Creator
{
    // 팩토리 메서드
    public abstract Product CreateProduct();

    // 생성된 제품을 사용하는 메서드
    public void UseProduct()
    {
        Product product = CreateProduct();
        product.Use();
    }
}

// ConcreteCreatorA 클래스 정의
public class ConcreteCreatorA : Creator
{
    public override Product CreateProduct()
    {
        return new ConcreteProductA();
    }
}

// ConcreteCreatorB 클래스 정의
public class ConcreteCreatorB : Creator
{
    public override Product CreateProduct()
    {
        return new ConcreteProductB();
    }
}

// 사용 예시
class Program
{
    static void Main(string[] args)
    {
        Creator creatorA = new ConcreteCreatorA();
        Creator creatorB = new ConcreteCreatorB();

        creatorA.UseProduct();  // 출력: Using Product A
        creatorB.UseProduct();  // 출력: Using Product B
    }
}

위 코드를 살펴보면,
Use() 메서드를 정의하는 Product 인터페이스를 상속받는 ConcreteProductAConcreteProductB 클래스에서 Product 인터페이스가 구현되어 있습니다.
Creator 추상 클래스는 CreateProduct()라는 팩토리 메서드를 추상 메서드로 정의하고, 하위 클래스 CreateCreatorA 그리고 CreateCreatorB에서 추상 메서드를 오버라이드 해줍니다.
그리고 실제로 Product객체를 생성할 때는 CreateCreatorA 또는 CreateCreatorB 클래스를 사용합니다.

이렇게 패턴을 작성함으로서, 다른 Product객체가 추가된다 해도 Creator 추상 클래스의 수정없이 새로운 Product객체에 해당하는 Creator 클래스를 새로 작성하는 것으로 확장이 가능해집니다.
(ex: ProductC 추가 -> CreateCreatorC 클래스 작성)

추상 팩토리 패턴

추상 팩토리 패턴은 연관된 객체들의 집합을 생성하는 인터페이스를 제공하는 패턴입니다. 팩토리 메서드 패턴에서 단일 객체를 생성하는 것과 달리, 여러 객체을 생성합니다.
여러 객체를 생성하는 메서드를 인터페이스를 통해 제공하고, 이를 상속받는 팩토리 클래스에서 그 메서드를 구현합니다.

  • 추상 팩토리 (인터페이스) : 서로 관련된 객체들을 생성하는 메서드를 정의
  • 실질 팩토리 : 추상 팩토리를 구현해서 각 제품군의 객체들을 생성
  • 추상 객체 (인터페이스) : 여러 객체들의 공통 인터페이스를 정의
  • 객체 : 추상 객체에서 정의한 인터페이스를 구현한 클래스로, 팩토리에서 생성

예시 코드 :

// Abstract ProductA
public interface AbstractProductA
{
    void UseA();
}

// Abstract ProductB
public interface AbstractProductB
{
    void UseB();
}

// Concrete ProductA1
public class ConcreteProductA1 : AbstractProductA
{
    public void UseA()
    {
        Console.WriteLine("Using Product A1");
    }
}

// Concrete ProductA2
public class ConcreteProductA2 : AbstractProductA
{
    public void UseA()
    {
        Console.WriteLine("Using Product A2");
    }
}

// Concrete ProductB1
public class ConcreteProductB1 : AbstractProductB
{
    public void UseB()
    {
        Console.WriteLine("Using Product B1");
    }
}

// Concrete ProductB2
public class ConcreteProductB2 : AbstractProductB
{
    public void UseB()
    {
        Console.WriteLine("Using Product B2");
    }
}

// Abstract Factory (객체 집합 생성 인터페이스)
public interface AbstractFactory
{
    AbstractProductA CreateProductA();
    AbstractProductB CreateProductB();
}

// Concrete Factory 1
public class ConcreteFactory1 : AbstractFactory
{
    public AbstractProductA CreateProductA()
    {
        return new ConcreteProductA1();
    }

    public AbstractProductB CreateProductB()
    {
        return new ConcreteProductB1();
    }
}

// Concrete Factory 2
public class ConcreteFactory2 : AbstractFactory
{
    public AbstractProductA CreateProductA()
    {
        return new ConcreteProductA2();
    }

    public AbstractProductB CreateProductB()
    {
        return new ConcreteProductB2();
    }
}

// 사용 예시
class Program
{
    static void Main(string[] args)
    {
        AbstractFactory factory1 = new ConcreteFactory1();
        AbstractFactory factory2 = new ConcreteFactory2();

        AbstractProductA productA1 = factory1.CreateProductA();
        AbstractProductB productB1 = factory1.CreateProductB();
        
        AbstractProductA productA2 = factory2.CreateProductA();
        AbstractProductB productB2 = factory2.CreateProductB();

        productA1.UseA(); // 출력: Using Product A1
        productB1.UseB(); // 출력: Using Product B1

        productA2.UseA(); // 출력: Using Product A2
        productB2.UseB(); // 출력: Using Product B2
    }
}

위 코드를 살펴보면,
관련있는 객체를 묶어주는 AbstractProductAAbstractProductB 인터페이스가 있으며, ConcreteProductA1, ConcreteProductA2 클래스는 AbstractProductA 인터페이스를, ConcreteProductB1, ConcreteProductB2 클래스는 AbstractProductB 인터페이스를 상속받아서 인터페이스를 구현하고 있습니다.
그리고 AbstractFactory 인터페이스는 연관있는 객체들의 집합을 생성하는 메서드를 정의하고 있으며, 이 인터페이스를 상속받는 ConcreteFactory1ConcreteFactory2 클래스에서 각각 메서드를 정의하고 서로 다른 객체 집합을 생성합니다.

이렇게 패턴을 작성함으로서, 객체 집합을 생성할 때, 한 팩토리에서 담당하게 설계할 수 있으며, 팩토리 메서드 패턴과 동일하게 OCP 원칙을 지키면서 코드를 작성할 수 있습니다.

ex:

  • 몬스터를 소환한다 가정할 때, OrcFactory 팩토리 클래스에서 Orc, BattleAxe, Leather Armor 객체를 같이 생성. (각각 AbstractFactory, Monster, Weapon, Armor 인터페이스를 상속받음)
  • 나중에 몬스터로 Ghost가 추가되어도, 새롭게 GhostFactory 클래스를 작성하고, 그 안에서 메서드를 오버라이드해서 Ghost, GhostHand, WhiteCloth 객체들을 생성




마지막으로 제가 사용했었던 심플 팩토리 코드입니다. 그리 효율적이진 못하니 참고만 해주세요.

public class CharacterStatsInitFactory
{
    public static ArisuStats InitArisuStats()
    {
        return new ArisuStats
        {
            skillDamage = 50,
            skillSpeed = 30,
            manaCost = 50,
            skillCoolDown = 2f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static MidoriStats InitMidoriStats()
    {
        return new MidoriStats
        {
            skillDamage = 10,
            skillSpeed = 30,
            manaCost = 30,
            skillCoolDown = 3f,
            detectionAngle = 120f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static MomoiStats InitMomoiStats()
    {
        return new MomoiStats
        {
            skillDamage = 5,
            skillSpeed = 30,
            manaCost = 20,
            skillCoolDown = 3f,
            bulletCount = 10,
            spreadAngle = 90f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }

    public static UZStats InitUZStats()
    {
        return new UZStats
        {
            skillDamage = 10,
            skillSpeed = 15,
            skillDuration = 4f,
            manaCost = 80,
            skillCoolDown = 10f,
            playerSpeed = 5,
            playerDamage = 1,
            maxHealth = 10,
            maxMana = 100,
            manaRegenPerSecond = 20f
        };
    }
}
profile
당신을 한 줄로 소개해보세요

0개의 댓글