-
Factory Pattern디자인 패턴 2022. 2. 14. 18:12
해당 책을 통해 공부한 내용을 정리하고 있습니다.
클래스 생성을 처리하는 클래스를 ‘팩토리’라고 부른다. 프로그램을 개발하다보면 객체를 생성할 일이 정말 많은데 이를 이곳 저곳에 흩어놓는 것이 아니라, 한 클래스에 캡슐화 해두면 그 클래스에만 들어가서 처리하면 되므로 훨씬 간단하게 프로그램을 관리 할 수 있다.
팩토리에는 3가지가 있다.
- 심플 팩토리
- 팩토리 메소드 패턴
- 추상 팩토리 패턴
해당 팩토리들을 하나하나 정리해보자.
심플 팩토리
심플 팩토리는 ‘디자인 패턴’이라고는 할 수 없고 객체지향에서 주로 사용하는 ‘관용구’에 가깝다. 이름 그대로 단순히 어떤 객체를 생성하는 클래스를 캡슐화 해둔 것이다. 예를 들어 피자를 파는 피자가게를 생각해보면 피자 객체의 생성만을 처리하는 공장을 따로 두는 것을 의미한다. 밑의 사진을 보자.
위의 사진에서 Pizza가 심플 팩토리에 해당한다. SimplePizzaStore에서 orderPizza() 메소드를 실행하면 Pizza에서 일련의 행동을 수행 한 뒤 Pizza 객체를 리턴해주는 방식이다.
팩토리 메소드 패턴
팩토리 메소드 패턴의 공식 정의는 다음과 같다.
객체를 생성하기 위한 인터페이스를 정의하는데 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다. 팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 방식으로 동작한다.
생성하는 일을 심플 팩토리를 사용해서 캡슐화하면 될텐데 왜 굳이 팩토리 메소드 패턴을 사용할까? 그 이유는 저렇게 메소드로 나눔으로써 아예 클래스로 나누고 중간에 통제가 없는 심플 팩토리와는 달리 클래스를 생성 할 때 어떤 클래스를 생성 할 지 통제를 할 수 있기 때문이다. 예를들어, 위에서 소개한 피자 가게를 생각해보자. 피자 가게가 프랜차이즈라고 가정한다면 여러 분점이 있게 될 것인데, 이 분점에서 피자를 자르는 방식도 본점과 다르게 하고, 굽는 방식도 다른 등 전부 자기 마음대로 피자를 만든다면 문제가 생길 것이다. 이런 상황에서 팩토리 메소드 패턴을 적용한다면 중간에 행동을 통제하는 서브 클래스를 끼워넣음으로써 행동 방식을 통제 할 수 있게 된다.
위의 그림에서 createPizza()가 팩토리 메소드(추상 메소드)에 해당하는 부분으로, 서브 클래스에서 행동을 제어 할 수 있다.
밑의 코드와 같이 구현된다.
public abstract class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; pizza = createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } abstract Pizza createPizza(String type); }
public class NYPizzaStore extends PizzaStore{ @Override Pizza createPizza(String type) { if(type.equals("cheese")) { return new NYStyleCheesePizza(); } else if(type.equals("pepperoni")) { return new NYStylePepperoniPizza(); } else if(type.equals("clam")) { return new NYStyleClamPizza(); } else if(type.equals("veggie")) { return new NYStyleVeggiePizza(); } else { return null; } } }
public class ChicagoPizzaStore extends PizzaStore{ @Override Pizza createPizza(String type) { if(type.equals("cheese")) { return new ChicagoStyleCheesePizza(); } else if (type.equals("pepperoni")) { return new ChicagoStylePepperoniPizza(); } else if (type.equals("clam")) { return new ChicagoStyleClamPizza(); } else if (type.equals("veggie")) { return new ChicagoStyleVeggiePizza(); } else { return null; } } }
NYPizzaStore와 ChicagoPizzaStore를 보면 주어지는 String 값에 따라서 정해진 객체를 리턴하는 것을 볼 수 있다.
추상 팩토리 패턴
추상 팩토리 패턴의 정의는 다음과 같다.
인터페이스를 이용해 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성 할 수 있는 팩토리 패턴
뭔가 이름도 거창해보이고, UML도 복잡해보인다. 하지만 팩토리 메소드와의 차이점을 안다면 비교적 쉽게 이해 할 수 있다. 팩토리 메소드와의 차이점은, 팩토리 메소드는 ‘하나’의 상품을 만드는데 최적화 되어있다면, 추상 팩토리 패턴은 여러 개의 상품을 만드는데 최적화 되어있다. 밑의 그림을 보자.
위의 그림은 팩토리 메소드 패턴의 UML로써 하나의 생산품을 만들 수 있는 형태이다. 만약 이것이 세개가 되면 어떨까?
Factory를 구현하는 모든 ConcreteFactor 클래스는 무조건 productA, productB, productC를 create를 하는 구문을 만들어야 할 것이다. 또한 만약 productB를 만들 필요가 없는 팩토리가 있다면 인터페이스를 상속받지 않고 만들어야 하는가? 등의 많은 골칫거리도 생겨날 것으로 보인다. 또 프로덕트가 이것보다 많아지면 굉장히 복잡해질듯 하다.
이런 고민을 해결하기 위해 태어난 것이 추상 팩토리 패턴이다. 아이디어는 공통점을 묶어서 팩토리를 여러개 묶고, 상품등을 묶는것이다. 예를들어 피잣집에서지점마다 그라나파다노 치즈를 사용하기도 하고, 페코리오 로마노 치즈를 사용하기도 한다고 가정하면 이들의 공통점인 ‘치즈’를 인터페이스로 묶는 방식이다. 또는 비슷한 기능이지만 조금씩 다른 Mac에서의 기능, Window에서의 기능, Linux에서의 기능등의 방식으로 묶으면 된다.
위의 그림을 보면 Product의 공통점별로 묶어 A, B, C로 묶은것을 볼 수 있고, FactoryA는 1번 프로덕트를, FactoryB는 2번 프로덕트를, FactoryC는 3번 프로덕트를 생산하는 모습을 볼 수 있다. 이런식으로 추상 팩토리 메소드를 사용하면 프로그램의 큰 복잡성 증가 없이 팩토리 패턴을 사용 할 수 있다.
이제 첫번째로 나왔던 그림을 보자. ConcreteFactoryA와 ConcreteFactoryB가 팩토리에 따라 A1, B1 제품과 A2, B2 제품을 나눠서 선택하는 것을 볼 수 있다. 여기서 Client는 사용자가 FactoryA를 선택하는지, FactoryB를 선택하는지 결정하는 역할을 한다.
이렇게 세개의 팩토리 패턴을 공부했다. 팩토리 패턴에서 중점적으로 사용하는 디자인 원칙은 DIP(Dependency Inversion Pattern)이다. 구상 클래스에 의존하지 말고 추상화 된 것에 의존하라는 원칙으로 활용하면 보다 유연하고 튼튼한 프로그램을 만들 수 있으니 지키도록 하자.
'디자인 패턴' 카테고리의 다른 글
Adapter Pattern (0) 2022.02.25 Command Pattern (0) 2022.02.23 Decorator Pattern (0) 2022.02.08 Observer Pattern (0) 2022.02.03 Strategy Pattern (0) 2022.02.02