HaPpY hApPy

Abstract Factory Pattern 본문

DesignPattern (디자인패턴)/Head First Design Pattern

Abstract Factory Pattern

juniguya 2013. 7. 30. 11:40

음,,,대략적인 이해는 가는데 전체 이해가 안간다.


일단 C# 소스 짜여져있는거 보면서 옮기면서 했는데,


FactoryPattern - Abstract.zip



나중에 좀더 세밀하게 봐야겠다.


예제에서는 피자 가계를 예를들고있다,


피자 가계마다(NewYork, Chicago) 만드는 피자 종류는 같다.(CheesePizza, ClamPizza)


하지만 들어가는 재료(Dough, Sauce, Cheese, veggies ,Pepperoni, Clam) 와

요리하는 방법(ThickCrustDough, ThinCrustDough 과 NaNaCheese, ReggianoCheese)은 다를수있다.


유심히 봐야할 부분은


Pizza클래스의 Prepare()함수가 어떻게 구현되는지,


PizzaStore 클래스의 CreatePizza() 함수를 ChicagoPizzaStore와 NewYorkPizzaStore가 어떻게 override해서

사용하는지 정도 되겠다. 뭐,,,Cut함수를 Override하기 위해서 허접한 방법을 사용한

CheesePizza Class의 Cut()함수도 한번 볼만하다. 이부분은 팩토리 패턴에서는 같은 종류의 CheesePizza 도

서로 다른 가계에서 서로 다르게 NYStyleCheesePizza와 ChicagoStyleCheesePizza 로 구현해서

Cut()을 각각 클래스 안에서 override하면 됐지만 여기서는 하나의 CheesePizza 클래스 밖에 없어서

override할때 어떤 객체로 생성이 되었는지 비교후에 출력을 해야한다.



먼저 피자 클래스

 public abstract class Pizza
    {
        protected string name;

        protected IDough dough; //Interface를 has로 표현(그래야 언제든지 서브클래스에서 구현가능)
        protected ISauce sauce;
        protected IVeggies[] Veggies;
        protected ICheese cheese;
        protected IPepperoni pepperoni;
        protected IClams clam;

        public abstract void Prepare();

        public virtual void Bake()
        {
            Console.WriteLine("Bake for 25 minutes at 350"); //이부분도 나중에 Pizza를 상속받는 CheesePizza 혹은 ClamPizza에서 override가능
        }

        public virtual void Cut()
        {
            Console.WriteLine("Cutting the pizza into diagonal slices");//이부분도 CheesePizza 에서 이부분 override해서 사용하는법 나옴.
        }

        public virtual void Box()
        {
            Console.WriteLine("Place pizza in official PizzaStore box");
        }

        public void SetName(string name)
        {
            this.name = name;
        }
        public string GetName()
        {
            return name;
        }
        public void ToString() //이부분도,,,걍 제외
        {
        }
    }

인터페이스들을 has관계로 가지고있다.(상속이 아니다.) 언제든지 Runtime에 변경이 가능하다.

이를 상속해서 나온 CheesePizza 와 Clam Pizza (팩토리 패턴 처럼 NewYork, Chicago 마다 서로다른 (NYStyleCheesePizza,  ChicagoStyleCheesePizza클래스를 만들필요가 없다)


  class CheesePizza : Pizza
    {
        IPizzaIngredientFactory ingredientFactory;
        public CheesePizza(IPizzaIngredientFactory ingredientFactory) //뉴욕 피자
		{
            this.ingredientFactory = ingredientFactory;
		}

        public override void Prepare()
        {
            Console.WriteLine("Preparing " + name);
            dough = ingredientFactory.CreateDough();
            sauce = ingredientFactory.CreateSauce();
            cheese = ingredientFactory.CreateCheese();
        }

        public override void Cut()
        {
            if (ingredientFactory.GetType() == ((IPizzaIngredientFactory)new NYPizzaIngredientFactory()).GetType())
            {
                base.Cut();
            }
            else if (ingredientFactory.GetType() == ((IPizzaIngredientFactory)new ChicagoPizzaIngredientFactory()).GetType())
            {
                Console.WriteLine("Cutting the pizza into 12 diagonal ChicagoPizzaIngredientFactory slices");
            }
        }
    }

    class ClamPizza : Pizza //Factory Pattern에서는 각각 가계마다 서로다른 ClamPizza를 구현했지만 지금은 그냥 하나로 구현 대신
    {                       //Prepare에서 Runtime에 각 가계에 맞는 ClamPizza 재료들로 객체 생성
       
        IPizzaIngredientFactory ingredientFactory;
        public ClamPizza(IPizzaIngredientFactory ingredientFactory) //뉴욕이 될수도있고, Chicago가 될수도있음
		{
            this.ingredientFactory = ingredientFactory;
		}

        public override void Prepare() //Prepare를 override해서 CheesePizza와 다르게 좀더 많은 재료 추가~
        {
            Console.WriteLine("Preparing " + name);
            dough = ingredientFactory.CreateDough();
            sauce = ingredientFactory.CreateSauce();
            cheese = ingredientFactory.CreateCheese();
            clam = ingredientFactory.CreateClam();
            Veggies = ingredientFactory.CreateVeggies();
        }
    }

재료를 하나, 둘 더 넣고 빼고의 차이정도 구현

그리고 재료를 구현한 인터페이스

    public interface IPizzaIngredientFactory
    {
        IDough CreateDough();
        ISauce CreateSauce();
        ICheese CreateCheese();
        IVeggies[] CreateVeggies();
        IPepperoni CreatePepperoni();
        IClams CreateClam();
    }

이 재료들을 가지고 Chicago와 Newyork에서 재료하는 방법은 다르다.

public class NYPizzaIngredientFactory : IPizzaIngredientFactory
    {

        public NYPizzaIngredientFactory()
        {
        }

        public IDough CreateDough()
        {
            return new ThinCrustDough(); //뉴욕은 얇게
        }

        public ISauce CreateSauce()
        {
            return new MarinaraSauce();
        }

        public ICheese CreateCheese()
        {
            return new ReggianoCheese(); //레지아노? 치즈 사용
        }

        public IVeggies[] CreateVeggies()
        {
            IVeggies[] veggies = {new Garlic(), new Onion(), new Mushroom(), new RedPepper() }; //베지는 많이~
            return veggies;
        }

        public IPepperoni CreatePepperoni()
        {
            return new SlicedPepperoni();
        }

        public IClams CreateClam()
        {
            return new FreshClams();
        }
    }

  public class ChicagoPizzaIngredientFactory : IPizzaIngredientFactory
    {

        public ChicagoPizzaIngredientFactory()
        {
        }

        public IDough CreateDough()
        {
            return new ThickCrustDough(); //시카고는 빵을 두껍게 굽고
        }

        public ISauce CreateSauce()
        {
            return new MarinaraSauce(); //마린소스 사용
        }

        public ICheese CreateCheese()
        {
            return new NaNaCheese(); //나나 치즈 사용
        }

        public IVeggies[] CreateVeggies()
        {
            IVeggies[] veggies = {new Garlic(), new RedPepper() }; //베지는 2개만
            return veggies;
        }

        public IPepperoni CreatePepperoni()
        {
            return new SlicedPepperoni();
        }

        public IClams CreateClam()
        {
            return new FreshClams();
        }
    }

피자 스토어는 이전 팩토리 패턴에서 달라진게 없는거 같다.

    public abstract class PizzaStore
    {
        public PizzaStore()
        {
        }

        public Pizza OrderPizza(PizzaType type)
        {
            Pizza pizza;

            //이게 팩토리 패턴의 핵심이였다면
            pizza = CreatePizza(type); //객체 타입을 서브 클래스에서 정의한다. 
           

            //서브 클래스에서 정의된 데로 여기서 결과가  달라진다.
            pizza.Prepare(); //이게 Abstract Factory Pattern의 핵심이다.
            pizza.Bake();
            pizza.Cut();
            pizza.Box();

            return pizza;
        }
        
        protected abstract Pizza CreatePizza(PizzaType type); //각각의 피자 스토어 마다 CreatePizza를 override 해서 각 가계에 맞는 피자를 만든다.
    }

그리고 이를 상속 받는 NewYorkStore와 ChicagoStore

class NYPizzaStore : PizzaStore
    {
        public NYPizzaStore()
        {}

        protected override Pizza CreatePizza(PizzaType type)
        {
            Pizza pizza = null;
            IPizzaIngredientFactory ingredientFactroy = new NYPizzaIngredientFactory(); //여기서 뉴욕식 재료 를 넣어준다.

            switch (type)
            {
                case PizzaType.CHEESE:
                    pizza = new CheesePizza(ingredientFactroy); //치즈 피자를 만드는데 들어가는 재료가 뉴욕식이다.
                    pizza.SetName("NewYork Style Cheese Pizza");
                    break;

                case PizzaType.CLAM: //나머지 3개의 피자는 따로 구현하지 않았다.
                    pizza = new ClamPizza(ingredientFactroy);
                    pizza.SetName("NewYork Style Clam Pizza");
                    break;

                case PizzaType.PEPPERONI:
                case PizzaType.VEGGIE:
                default:
                    return null;
            }
            return pizza;
        }
    }

class ChicagoPizzaStore : PizzaStore
    {
        public ChicagoPizzaStore()
        { }

        protected override Pizza CreatePizza(PizzaType type)
        {
            Pizza pizza = null;
            IPizzaIngredientFactory ingredientFactroy = new ChicagoPizzaIngredientFactory(); //이부분만 다름

            switch (type)
            {
                case PizzaType.CHEESE:
                    pizza = new CheesePizza(ingredientFactroy); //들어가는 재료는 시카고
                    pizza.SetName("Chicago Style Cheese Pizza"); //물론 이름도 ㅋ
                    break;

                case PizzaType.CLAM: //나머지 2개의 피자는 따로 구현하지 않았다.
                    pizza = new ClamPizza(ingredientFactroy);
                    pizza.SetName("Chicago Style Clam Pizza");
                    break;

                case PizzaType.PEPPERONI: //시간나면 구현해보장~
                case PizzaType.VEGGIE:
                default:
                    return null;
            }
            return pizza;
        }
    }

마지막으료 재료들을 요리하는 방법이다.

 public class MarinaraSauce : ISauce
    {
        public MarinaraSauce()
        {
            Console.WriteLine("Marinara Sauce");
        }
    }

    public class ThickCrustDough : IDough
    {
        public ThickCrustDough()
        {
            Console.WriteLine("Thick Crust Dough");
        }
    }

    public class ThinCrustDough : IDough
    {
        public ThinCrustDough()
        {
            Console.WriteLine("Thin Crust Dough");
        }
    }

    public class ReggianoCheese : ICheese
    {
        public ReggianoCheese()
        {
            Console.WriteLine("Reggiano Cheese");
        }
    }

    public class NaNaCheese : ICheese
    {
        public NaNaCheese()
        {
            Console.WriteLine("NaNa Cheese");
        }
    }

    public class Garlic : IVeggies
    {
        public Garlic()
        {
            Console.WriteLine("Garlic");
        }
      
    }

    public class Onion : IVeggies
    {
        public Onion()
        {
            Console.WriteLine("Onion");
        }
    }

    public class Mushroom : IVeggies
    {
        public Mushroom()
        {
            Console.WriteLine("Mushroom");
        }
    }

    public class RedPepper : IVeggies
    {
        public RedPepper()
        {
            Console.WriteLine("Red Pepper");
        }
    }

    public class SlicedPepperoni : IPepperoni
    {
        public SlicedPepperoni()
        {
            Console.WriteLine("Sliced Pepperoni");
        }
    }

    public class FreshClams : IClams
    {
        public FreshClams()
        {
            Console.WriteLine("Fresh Clams");
        }
    }

각 Pizza를 상속받는 클래스들의 Prepare에서 골라 맞추면된다.


음,,,마지막에 정리하다보니 왜 이름이 팩토리 패턴인지 알겠다.ㅎㅎ