디자인 패턴

주상돈·2025년 3월 13일

TIL

목록 보기
38/53

팩토리(Factory) 패턴


팩토리 패턴(Factory Pattern)”은 객체 생성 로직을 별도의 “팩토리”(메서드·클래스·인터페이스 등)로 캡슐화하여,

클라이언트 코드가 특정 구체 클래스에 직접 의존하지 않도록 하는 생성(Creational) 디자인 패턴 계열을 가리킨다.

대표적인 종류

  • Factory Method 패턴
  • Abstract Factory 패턴
  • (확장) Simple Factory(심플 팩토리) 라고 불리는 간단한 변형도 많이 사용됨.

문제 상황 (Why Factory?)

어떤 클래스 내부(또는 클라이언트 코드)에서 직접 new()를 사용해 구체 클래스 객체를 직접 생성하면, 코드가 특정 구현 클래스에 강하게 결합(coupling)된다.

예를 들어 Document doc = new WordDocument(); 라는 부분이 있을 때, 나중에 해당 부분을 PdfDocument로 교체하고 싶다면 관련 코드를 찾아서 전부 수정해야 한다.

새로운 제품 클래스가 추가되거나, 기존 생성 로직이 바뀌면, 객체를 생성하는 부분마다 수정이 필요하다.

이로 인해 OCP(Open/Closed Principle), DIP(Dependency Inversion Principle) 등을 지키기 어려워진다.

따라서 객체 생성 로직을 한곳(팩토리)에만 두면, 생성 방식 변경 시 이 로직만 수정하면 되므로 유지보수가 편해진다.

팩토리 메서드(Factory Method) 패턴

개념

“팩토리 메서드” 패턴은 추상 클래스(또는 인터페이스)가 객체 생성을 위한 추상 메서드(또는 오버라이드 가능한 메서드)를 정의하고,

이 메서드를 구상 클래스(서브클래스)에서 구현(오버라이드)하여 구체 객체를 생성하도록 하는 구조이다.

즉, “어떤 객체를 생성할지?”에 대한 책임을 하위 클래스에게 위임하므로, 클라이언트는 추상 팩토리 메서드만 알고 있으면 된다.

언제 쓰는가?

  1. 서브클래스(별도 Creator)에 따라 다른 종류의 객체를 생성해야 할 때
    • 예: 문서 애플리케이션(TextApplication vs DrawingApplication), 게임 지역별 몬스터(ForestSpawner vs CaveSpawner) 등.
  2. 객체 생성 과정을 재정의(오버라이드)하고 싶을 때
    • 프레임워크가 골격(Creator)만 두고, 사용자가 커스텀 서브클래스에서 생성 방식을 확장/재정의
  3. if-else / switch 분기를 줄이고, 새로운 구체 클래스 추가 시 기존 코드 수정을 최소화하고 싶을 때

구조

  1. Creator(추상 클래스/인터페이스)
    • 팩토리 메서드(예: CreateProduct())를 정의.
    • 필요 시 객체 생성 전후 공통 작업(훅 메서드)을 구현.
  2. Concrete Creator(구상 서브클래스)
    • 추상 Creator를 상속받아 팩토리 메서드를 구체 구현(예: return new ConcreteProductA();).
    • 어떤 구체 클래스 객체를 반환할지 결정.

간단 예시 1) 문서 애플리케이션

// (1) 구상 Creator 클래스 사용될 인터페이스 & 구현 클래스
public interface IDocument
{
    void Open();
    void Close();
    void Save();
}
public class WordDocument : IDocument { /* ... */ }
public class PdfDocument  : IDocument { /* ... */ }

// (2) 추상 Creator 클래스
public abstract class Application
{
    protected abstract IDocument CreateDocument();  // 팩토리 메서드

    public IDocument NewDocument()
    {
        IDocument doc = CreateDocument();
        doc.Open();
        return doc;
    }

    public void SaveDocument(IDocument doc) => doc.Save();
}

// (3) 구상 Creator 클래스 
public class WordApplication : Application
{
    protected override IDocument CreateDocument() => new WordDocument();
}

public class PdfApplication : Application
{
    protected override IDocument CreateDocument() => new PdfDocument();
}

// (4) 사용
class Program
{
    static void Main()
    {
        Application wordApp = new WordApplication();
        IDocument wordDoc = wordApp.NewDocument();
        wordApp.SaveDocument(wordDoc);

        Application pdfApp = new PdfApplication();
        IDocument pdfDoc = pdfApp.NewDocument();
        pdfApp.SaveDocument(pdfDoc);
    }
}
  • WordApplication이 내부적으로 WordDocument를 생성하게 되므로, 클라이언트는 직접 WordDocumentnew 할 필요가 없다.

간단 예시 2) 게임 몬스터 스포너

// (1) 구상 Creator 클래스 사용될 인터페이스 & 구현 클래스
public interface IMonster 
{ 
	void Spawn(); 
	void Attack(); 
}
public class Slime   : IMonster { /* ... */ }
public class Goblin  : IMonster { /* ... */ }

// (2) 추상 Creator 클래스
public abstract class MonsterSpawner
{
    protected abstract IMonster CreateMonster(); // 팩토리 메서드

    public void SpawnMonster()
    {
        IMonster monster = CreateMonster();
        monster.Spawn();
        monster.Attack();
    }
}

// (3) 구상 Creator 클래스
public class ForestSpawner : MonsterSpawner
{
    protected override IMonster CreateMonster() => new Slime();
}

public class CaveSpawner : MonsterSpawner
{
    protected override IMonster CreateMonster() => new Goblin();
}

// (4) 사용
public class GameFactoryExample
{
    public static void Main()
    {
        MonsterSpawner forest = new ForestSpawner();
        MonsterSpawner cave   = new CaveSpawner();

        forest.SpawnMonster(); // 슬라임이 등장
        cave.SpawnMonster();   // 고블린이 등장
    }
}

Factory Method 패턴의 장단점

  • 장점
    1. 구체 클래스 의존 최소화: 클라이언트는 추상 Creator/인터페이스만 알고 있어도 됨.
    2. OCP(개방-폐쇄) 원칙: 새로운 제품 클래스가 추가되면, Creator 하위 클래스만 작성하면 됨.
    3. 유연성: 생성될 객체 종류를 서브클래스로 교체하여 쉽게 변경 가능.
  • 단점
    1. 클래스 수 증가: Creator 추상 클래스 + 여러 구상 Creator로 인해 클래스가 많아질 수 있음.
    2. 분기 로직이 (특히 단일 Creator 내 여러 분기가 필요할 때) 일부 존재할 수 있음.
    3. 과설계 위험: 단순한 경우, 그냥 new를 쓰는 편이 더 직관적일 수 있음.

추상 팩토리(Abstract Factory) 패턴


개념

추상 팩토리 패턴은 “서로 연관된 (또는 의존 관계가 있는) 복수의 객체(제품군)를 한꺼번에 생성”해야 할 때 사용하는 패턴.

예: Windows UI 세트(WindowsButton + WindowsCheckbox), Mac UI 세트(MacButton + MacCheckbox)처럼 서로 호환되는 객체들을 일관되게 만들고 싶을 때 사용.

구조

  1. 추상 팩토리(인터페이스/추상 클래스)
    • 일련의 팩토리 메서드(CreateButton(), CreateCheckbox() 등)를 정의.
  2. 구체 팩토리(Concrete Factory)
    • 추상 팩토리를 구현하여, 특정 제품군에 맞는 객체들을 생성(Windows 제품군, Mac 제품군 등).
  3. 추상 제품(인터페이스/추상 클래스)
    • 각 제품군이 구현해야 할 공통 인터페이스(예: IButton, ICheckbox).
  4. 구체 제품(Concrete Product)
    • 추상 제품 인터페이스를 구현한 실제 클래스(WindowsButton, MacButton 등).

예시: UI 컴포넌트

// (1) 인터페이스
public interface IButton   
{ 
	void Render(); 
}

public interface ICheckbox 
{ 
	void Render(); 
}

// (2) 구체 제품
public class WindowsButton : IButton   
{ 
	public void Render() => Console.WriteLine("Windows 버튼"); 
}

public class MacButton : IButton   
{ 
	public void Render() => Console.WriteLine("Mac 버튼"); 
}

public class WindowsCheckbox : ICheckbox 
{ 
	public void Render() => Console.WriteLine("Windows 체크박스"); 
}

public class MacCheckbox : ICheckbox 
{ 
	public void Render() => Console.WriteLine("Mac 체크박스"); 
}

// (3) 추상 팩토리
public interface IGUIFactory
{
    IButton   CreateButton();
    ICheckbox CreateCheckbox();
}

// (4) 구체 팩토리
public class WindowsFactory : IGUIFactory
{
    public IButton CreateButton()   => new WindowsButton();
    public ICheckbox CreateCheckbox() => new WindowsCheckbox();
}

public class MacFactory : IGUIFactory
{
    public IButton CreateButton()   => new MacButton();
    public ICheckbox CreateCheckbox() => new MacCheckbox();
}

// (5) 사용
public class AbstractFactoryExample
{
    private IButton _button;
    private ICheckbox _checkbox;

    public AbstractFactoryExample(IGUIFactory factory)
    {
        _button   = factory.CreateButton();
        _checkbox = factory.CreateCheckbox();
    }

    public void RenderUI()
    {
        _button.Render();
        _checkbox.Render();
    }

    public static void Main()
    {
        IGUIFactory windowsFactory = new WindowsFactory();
        AbstractFactoryExample winApp = new AbstractFactoryExample(windowsFactory);
        winApp.RenderUI();        // Windows 버튼, Windows 체크박스


        IGUIFactory macFactory = new MacFactory();
        AbstractFactoryExample macApp = new AbstractFactoryExample(macFactory);
        macApp.RenderUI();         // Mac 버튼, Mac 체크박스
    }
}

Abstract Factory 패턴의 장단점

  • 장점
    1. 제품군 일관성: 한 팩토리를 통해 생성되는 객체들은 서로 호환성을 가짐(버튼·체크박스가 같은 OS 스타일 등).
    2. 의존성 감소: 클라이언트는 추상 팩토리 인터페이스만 의존하므로, 구체 제품들을 몰라도 됨.
    3. 확장 용이: 새로운 제품군(예: LinuxFactory)을 추가해도, 기존 코드를 크게 수정할 필요 없이 팩토리만 구현하면 됨.
  • 단점
    1. 클래스/구현체 증가: 제품 종류가 많아질수록, 팩토리와 제품군 구현도 늘어날 수 있음.
    2. 단순한 경우 과설계: 생성해야 할 객체가 적거나 제품군 개념이 희미하다면, 오히려 복잡해 보일 수 있음.

심플 팩토리(Simple Factory)


“심플 팩토리”는 “정적 메서드 기반”의 간단한 팩토리 기법.

별도의 팩토리 클래스(또는 팩토리 메서드) 하나만 만들어놓고, CreateXXX() 같은 메서드 내부에서 if-elseswitch를 사용하여 구체 객체를 생성해 반환.

public static class DocumentFactory
{
    public static IDocument CreateDocument(string type)
    {
        if(type == "Word")
            return new WordDocument();
        else if(type == "PDF")
            return new PdfDocument();
        else
            throw new ArgumentException("Unknown Document Type");
    }
}
// 사용
IDocument doc1 = DocumentFactory.CreateDocument("Word");
IDocument doc2 = DocumentFactory.CreateDocument("PDF");

심플 팩토리(Simple Factory)의 장단점

  • 장점
    1. 객체 생성 로직을 한곳에 모아둘 수 있어서, 클라이언트에서는 단순히 CreateDocument("Word")처럼 사용.
    2. 구체 클래스를 직접 new 하지 않으므로, 클라이언트가 구현 클래스에 의존하지 않음.
    3. 새로운 타입이 추가될 때, “심플 팩토리”만 수정하면 되므로(그래도 여러 군데 new를 직접 고치는 것보다는 낫다).
  • 단점
    1. if-else / switch 분기 로직이 많아지면, 코드가 비대해질 수 있음.
    2. 새로운 제품 추가 시에도 결국 “심플 팩토리” 내부 로직은 수정이 필요함(다만, 수정 범위가 공통 팩토리 하나에 제한된다는 장점이 있음).

팩토리 패턴들 간 비교 & 마무리


패턴목적특징
Factory Method- “하나의 제품” 생성 로직을 서브클래스가 결정하도록 함
- 추상 Creator 클래스에서 팩토리 메서드를 정의- 추상 Creator & 구상 Creator 클래스
- 상속(오버라이딩) 기반의 구조
Abstract Factory- “서로 연관된 복수의 제품군”을 일관되게 생성- 여러 팩토리 메서드(버튼, 체크박스 등)
- 제품군의 일관성과 호환성 보장
Simple Factory- 별도 팩토리 클래스(혹은 정적 메서드)에서 간단히 객체 생성 로직을 캡슐화- 공통 분기(if-else/switch) 사용

공통 장점

  • OCP(개방-폐쇄 원칙), DIP(의존성 역전 원칙) 등 SOLID 원칙 준수에 도움이 됨.
  • 객체 생성 로직을 캡슐화하여, 클라이언트 코드의 수정 범위를 최소화.
  • 유연성: 팩토리만 교체(또는 확장)하면, 생성되는 객체를 손쉽게 변경할 수 있음.

공통 단점

  • 클래스와 구조가 다소 복잡해질 수 있음(특히 Factory Method & Abstract Factory).
  • 분기 로직이 팩토리 쪽에 몰릴 수 있음.
  • 과설계 위험: 간단한 경우엔 그냥 new로 생성하는 게 더 나을 수도 있음.

결론

  • Factory Method 패턴
    • 하나의 제품 생성에 대해, “무엇을 생성할지”를 하위 클래스(구상 Creator)로 위임.
    • 주로 상속과 오버라이드를 이용한 유연한 구조.
  • Abstract Factory 패턴
    • 서로 연관된 여러 객체(제품군)를 일관되게 생성해야 할 때 사용.
    • 하나의 “팩토리 인터페이스”로 여러 팩토리 메서드를 제공.
  • Simple Factory(심플 팩토리)
    • 별도 팩토리 클래스 하나 두고, if-elseswitch로 구체 객체를 생성하는 간단한 방식.

상황에 따라 각 패턴을 적절히 선택하면, 확장성·유지보수성을 높일 수 있다.

  • 제품이 단일하고 상속구조를 써야 한다면 Factory Method
  • 제품군이 여러 개고 서로 관련성이 있다면 Abstract Factory
  • 가볍게 “팩토리”를 도입하고 싶다면 Simple Factory를 고려해볼 수 있다.

0개의 댓글