2. Factory Method Pattern ( 팩토리 매소드 패턴 C# )

|

[읽기전에]

  UML 다이어그램 : UML클래스 다이어그램 기본상식 http://hongjinhyeon.tistory.com/25 

  포스팅되는 디자인 패턴의 예는 스타크래프트를 기본으로 하였습니다 : 디자인 패턴을 시작하며 http://hongjinhyeon.tistory.com/24


<기본정의 >

1. Factory Method Pattern 정의

   -객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정

    팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기게 된다.

    ( Define an interface for creating an object, but let subclasses decide which class to instantiate. 

      Factory Method lets a class defer instantiation to subclasses.  )


  -팩토리 메소드 패턴을 사용하는데에는 몇가지 이유가 있지만, 결론적으로 인터페이스를 가지는 클래스를 생성하는 것입니다.

   즉, 동일한 인터페이스를 준수하는 클래스들을 생성을 합니다. 

   

 

2. UML Diagram



3. 사용목적 및 용도, 장점

 -객체의 생성을 한군데에서 관리를 할수가 있다. (ConcreteCreator 부분에서만 생성코드를 넣는다. )

 -동일한 인터페이스 구현으로 새로운 객체가 추가되더라도 소스의 수정의 거의 없다. ( 생성 부분의 수정과 신규 클래스의 추가 정도 )

 -객체를 여러군데에서 생성을 각자하면 동일한 생성을 보장 못하지만, 한군데에서 관리하게 되면 동일한 생성을 보장한다.


4. 소스


using System;
 
namespace DoFactory.GangOfFour.Factory.Structural
{
  /// <summary>
  /// MainApp startup class for Structural
  /// Factory Method Design Pattern.
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Entry point into console application.
    /// </summary>
    static void Main()
    {
      // An array of creators
      Creator[] creators = new Creator[2];
 
      creators[0] = new ConcreteCreatorA();
      creators[1] = new ConcreteCreatorB();
 
      // Iterate over creators and create products
      foreach (Creator creator in creators)
      {
        Product product = creator.FactoryMethod();
        Console.WriteLine("Created {0}",
          product.GetType().Name);
      }
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// <summary>
  /// The 'Product' abstract class
  /// </summary>
  abstract class Product
  {
  }
 
  /// <summary>
  /// A 'ConcreteProduct' class
  /// </summary>
  class ConcreteProductA : Product
  {
  }
 
  /// <summary>
  /// A 'ConcreteProduct' class
  /// </summary>
  class ConcreteProductB : Product
  {
  }
 
  /// <summary>
  /// The 'Creator' abstract class
  /// </summary>
  abstract class Creator
  {
    public abstract Product FactoryMethod();
  }
 
  /// <summary>
  /// A 'ConcreteCreator' class
  /// </summary>
  class ConcreteCreatorA : Creator
  {
    public override Product FactoryMethod()
    {
      return new ConcreteProductA();
    }
  }
 
  /// <summary>
  /// A 'ConcreteCreator' class
  /// </summary>
  class ConcreteCreatorB : Creator
  {
    public override Product FactoryMethod()
    {
      return new ConcreteProductB();
    }
  }
}


4. 실행 결과

Created ConcreteProductA

Created ConcreteProductB


<실제 적용>

1. UML Diagram



2. 사용목적 및 용도

 -스타크래프트에서 팩토리 메소드 패턴을 적용에 가장 알맞은 곳은 바로 유닛 생성부분이다.  단순히 팩토리 메소드 패턴만으로는

   분명 한계가 존재하지만, ( 실제로는 여러개의 패턴이 어우러져서 적용되었을 것이다.) 좋은 예가 된다.


 -스타크래프트에서  건물에서 생성되는 유닛들은 모두 비슷한 명령들을 가지고 있다. 이동/공격/죽음/멈춤등의 액션 존재하는데 

   이 액션들이 Unit에서 구현될 인터페이스가 되며 Marine이나 Dropship같은 객체들은 해당 인터페이스에대한 각자 특색있는 기능

   을 구현하게 된다 ( 예를 들어, Marine은 이동을 하면 지상을 이동하지만 Dropship은 공중에서 이동이 된다. )


 -Barracks에서 유닛을 생성할때, 유닛 빌드타임(유닛 생성시간) -> 유닛을 전역변수에 등록 -> 유닛 생성 종료 알림-> 유닛 화면 표출 등의

  일련의 과정들이 일어나게 되는데 이것들은 Barracks의 유닛 생성 부분에서 정의를 하게 해서, 여러군데에서 유닛을 만들때마다 하는 작업

  을 줄일수가 있으며 하나를 빼먹는 실수를 하지 않을 수가 있다.


 -Barracks가 생산할 수 있는 신규 유닛이 추가 할때는, 간단히 Unit의 인터페이스를 구현하는 클래스를 만들고 Barracks에서 해당 유닛을

  생성하는 부분만 추가해주면 나머지는 수정이 필요가 없게된다. ( 사용자가 유닛을 다루는 부분에서는 실제 Marine객체를 가지고 하는것이

  아니라 Unit이라는 상위 추상 객체를 이용하기 때문에 신규 객체가 추가되더라도 소스의 수정이 없게되는것이다. )


3. 소스


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GOF
{
    class Program
    {
        static void Main(string[] args)
        {
            Building[] Buildings = new Building[2];

            Buildings[0] = new Barracks();
            Buildings[1] = new StarPort();

            List<Unit> ltAllUnit = new List<Unit>();
            ltAllUnit.Add(Buildings[0].makeUnit("적 마린"));
            ltAllUnit.Add(Buildings[1].makeUnit("아군 드랍쉽"));

            ltAllUnit[0].Move("언덕");
            Unit unitMarine = ltAllUnit[0];
            ltAllUnit[1].Attacked(ref unitMarine);
           
            Console.ReadLine();
        }

    }

    public abstract class Unit
    {

        public string m_strName;
        public int m_intAttackPower;
        public int m_intHealth;

        public abstract void Move(string _strPoint);
        public abstract void Attacked(ref Unit _unitTarget);

    }

    public class Marine : Unit
    {

        public Marine(string _strName)
        {
            this.m_strName = _strName;
            this.m_intAttackPower = 15;
            this.m_intHealth = 100;
            Console.WriteLine(_strName + " : 생성 완료");
        }

        public override void Move(string _strPoint)
        {
            Console.WriteLine(m_strName + " : " + _strPoint + " 이동 완료");
        }


        public override void Attacked(ref Unit _unitTarget)
        {

            this.m_intHealth -= _unitTarget.m_intAttackPower;
            Console.WriteLine(m_strName + " 공격당함 : 공격자->" + _unitTarget.m_strName + " : 남은체력 " + this.m_intHealth.ToString());
        }


    }

    public class Dropship : Unit
    {

        public Dropship(string _strName)
        {
            this.m_strName = _strName;
            this.m_intAttackPower = 0;
            this.m_intHealth = 100;
            Console.WriteLine(_strName + " : 생성 완료");
        }


        public override void Move(string _strPoint)
        {
            Console.WriteLine(m_strName + " : " + _strPoint + " 이동 완료");
        }

        public override void Attacked(ref Unit _unitTarget)
        {
            this.m_intHealth -= _unitTarget.m_intAttackPower;
            Console.WriteLine(m_strName + " 공격당함 : 공격자->" + _unitTarget.m_strName + " : 남은체력 " + this.m_intHealth.ToString());
        }
    }

    public abstract class Building
    {
        public abstract Unit makeUnit(string _strName);
    }

    public class Barracks : Building
    {

        public override Unit makeUnit(string _strName)
        {
            return new Marine(_strName);
        }
    }

    public class StarPort : Building
    {

        public override Unit makeUnit(string _strName)
        {
            return new Dropship(_strName);
        }
    }

}


4. 결과


※피드백

 -ltAllUnit[0].Move("언덕") 여기에서 처럼 유닛에 상관없이 다양한 종류의 타입들을 동일한 코드로 처리가 가능하다.

  보통 스타크래프트에서 유닛들을 이동시킬때 마우스 스크롤로 지정해서 한번에 이동하는데, 마린이나 메딕 파이어벳등의

  다른 타입의 유닛들이 동시에 움직이게 된다.


 -스타크래프트 이외에 다른용도로는 많이 있겠지만, 데이터 베이스 접속을 할때도 응용이 가능하다. 보통 프로그램에서 DB를 

   접속해서 데이터를 처리하는데 하나의 DB만 있는것이 아니라 MSSQL(버전이 다양함), Mysql, Oracle 등등의 것들을 하나의

   인터페이스로 처리가 가능하다.


 -이 패턴을 알기전에는 DB 접속에 대해서 단순히 심플팩토리 와 인터페이스를 이용해서 구현을 하였다. 사용 및 효율면에서

  이 패턴과 거의 동일하게 동작한다. 확장성 및 소스유지관리 측면에서는 팩토리 메소드 패턴이 좋다.



And