팩토리 패턴(Factory Pattern)

탬플릿 메소드 패턴(template method pattern)

  • 소프트웨어 공학에서 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴
  • 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다
    • base class가 알고리즘 구조의 주요 골조를 확립
    • subclass가 나머지 부분을 채워주면서 특정 알고리즘을 수행하도록 확장

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

Duck duck;
if (picnic) {
    duck = new MallardDuck();
} else if (hunting) {
    duck = new DecoyDuck();
} else if (inBathTub) {
    duck = new RubberDuck();
}
  • 위와 같이 SubClass를 조건마다 생성하는 경우
    1. 클래스 생성이 하드코딩에 의해 이루어진다 (picnic, hunting, inBathTub, …)
    2. 클래스의 추가, 변경, 삭제 때마다 switch문이 수정되어야 한다
    3. 수정마다 재컴파일 비용이 발생한다
  • 팩토리 메서드 패턴은 템플릿 메소드 패턴의 일종으로, 부모 클래스에서 상속된 여러 자식 클래스 중 하나를 리턴하는 디자인 패턴
  • 사전 정의되지 않은, 또는 인터페이스로서 제공되거나 동적으로 정의된 컴포넌트를 가진 클래스를 생성
    • 탈것 클래스에서 모터 컴포넌트가 존재하지만, 모터의 구체적인 타입은 정의되지 않은 상태.
    • 추후 탈것 생성자에서 전기모터/가솔린모터 등으로 구체화 가능.
    • 탈 것 생성자 내부에서 모터 팩토리를 콜함
  • 사전 정의되지 않은 컴포넌트의 부모 클래스에 의해 동적 선언되는 또는 인터페이스만 제공되는 하위 클래스를 생성할 수 있도록 허용.
    • 탈것 클래스는 전기비행선과 구형차량 따위의 하위클래스로 선언될 수 있고, 멤버로 동적 할당될 타입의 모터를 가지고 있음.
    • 하위 클래스가 생성되는 시점에 모터도 어떤 타입이 선언될 지 결정됨.
    • 모터 타입이 히든으로 제공된다는 전제로 탈 것 팩토리를 이용해 하위 클래스를 선언하는 것이 가능
  • 여러 개의 생성자가 존재하더라도, 코드 가독성을 보장

      // 아래 2개 보다는
      Vehicle(make:string, motor:number);
      Vehicle(make:string, owner:string, license:number, purchased:date);
    
      // 이 2개가 더 이해하기 쉬움
      Vehicle.CreateOwnership(make:string, owner:string, license:number, purchased: date);
      Vehicle.Create(make:string, motor:number);
    
  • 상위 클래스의 접근을 차단하고 바로 하부 클래스의 객체화를 하도록 디자인
    • 탈것 클래스에 대한 생성자 접근을 외부로부터 차단
    • 오직 하위클래스에 대한 접근만을 허용하여 팩토리 메서드를 통해 생성되도록 허용
public abstract class Pizza
{
    public abstract decimal GetPrice();

    public enum PizzaType
    {
        HamMushroom, Deluxe, Seafood
    }

    // 아래 함수는 별도로 캡슐화 시켜 독립된 클래스로 선언할 수 있다.
    public static Pizza PizzaFactory(PizzaType pizzaType)
    {
        switch (pizzaType)
        {
            case PizzaType.HamMushroom:
                return new HamAndMushroomPizza();

            case PizzaType.Deluxe:
                return new DeluxePizza();

            case PizzaType.Seafood:
                return new SeafoodPizza();

        }

        throw new System.NotSupportedException("The pizza type " + pizzaType.ToString() + " is not recognized.");
    }
}

// 이제 새로운 유형의 피자와 기존 피자 클래스를 팩토리로부터 디커플링할 수 있다.
public class HamAndMushroomPizza : Pizza
{
    private decimal price = 8.5M;
    public override decimal GetPrice() { return price; }
}

public class DeluxePizza : Pizza
{
    private decimal price = 10.5M;
    public override decimal GetPrice() { return price; }
}

public class SeafoodPizza : Pizza
{
    private decimal price = 11.5M;
    public override decimal GetPrice() { return price; }
}

// Somewhere in the code
Console.WriteLine( Pizza.PizzaFactory(Pizza.PizzaType.Seafood).GetPrice().ToString("C2") ); // $11.50

빌더 패턴와 팩토리 패턴의 차이

  • 빌더 패턴은 복잡한 구조를 순차적으로 생성하는 데 초점이 맞춰져 있지만, 추상 팩토리 패턴은 객체의 파생 그룹 자체에 중점을 둔다.
  • 빌더는 여러 단계를 거쳐 마지막에 완성된 객체를 반환하지만, 추상 팩토리는 객체를 즉각 반환한다.
  • 빌더는 조합된 결과물을 생성한다.

추상 팩토리 패턴

  • 다양한 구성 요소 별로 ‘객체의 집합’을 생성해야 할 때 유용하다.
  • 이 패턴을 사용하여 상황에 알맞은 객체를 생성할 수 있다.
  • 이 패턴의 가장 밑바탕은 “하위 클래스들의 실구현 없이 오직 interface를 통해 객체의 연관 집합을 생성하는 것”이다.

추상 팩토리 패턴과 팩토리 메서드의 차이

  • 팩토리 메서드는 메서드이고, 추상 팩토리는 객체다.
  • 팩토리 메서드는 다른 하위 클래스에 의해 overriding이 가능하다.
    • base class에서 가진 메서드를 subclass에서 정의한다
    • … the Factory Method pattern uses inheritance and relies on a subclass to handle the desired object instantiation.
      class A {
        public void doSomething() {
            Foo f = makeFoo();
            f.whatever();   
        }
    
        protected virtual Foo makeFoo() {
            return new RegularFoo();
        }
      }
    
      class B extends A {
          protected override Foo makeFoo() {
              // 하위 클래스가 다른 객체를 반환하기 위해 overriding
              return new SpecialFoo();
          }
      }
    
  • 추상 팩토리는 여러 팩토리 메서드가 얽힌 하나의 객체다.
    • 여러 메서드를 가진 하나의 추상 팩토리를 선언하고, 이를 derive하는 실구현 메서드를 가진 새로운 팩토리를 디자인한다
    • … with the Abstract Factory pattern, a class delegates the responsibility of object instantiation to another object via composition …
      class A {
        private Factory factory;
    
        public A(Factory factory) {
            this.factory = factory;
        }
    
        public void doSomething() {
            //객체 f는 constructor에서 할당받은 factory에 의존한다.
            //만약 다른 factory를 할당했다면, foo의 값도 달라질 것이다.
            Foo f = factory.makeFoo();
            f.whatever();
        }
      }
    
      interface Factory {
          Foo makeFoo();
          Bar makeBar();
          Aycufcn makeAmbiguousYetCommonlyUsedFakeClassName();
      }
    
      //이하 interface Facotry를 실구현하는 concrete factory...
    

출처