-
[디자인패턴] 추상 팩토리 패턴 (Abstract-Factory Pattern)IT, 프로그래밍/Design Patterns 2017. 12. 10. 19:51
추상 팩토리 패턴 (Abstract-Factory Pattern) 에서는 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정 하지 않고도 생성할 수 있습니다.
이말은 즉, 우리가 팩토리 메소드 편에 보았던 JPStyleBrownShoes, FRStyleRedShoes .. 이런 식으로 추상클래스에 의존 하는 구상 클래스를 만들지 않고도 생성할 수 있다는 뜻이죠.
디자인 패턴을 사용 하지 않았을때는?
123456789101112131415161718192021222324252627282930313233343536373839404142434445class DependentShoesStore {public Shoes makeShoes(String style, String name) {Shoes shoes = null;if (style.equals("Japan")) {if (name.equals("blackShoes")) {shoes = new JPStyleBlackShoes();} else if (name.equals("brownShoes")) {shoes = new JPStyleBrownShoes();}else if(name.equals("redShoes")) {shoes = new JPStyleRedShoes();}}else if(style.equals("france")) {if (name.equals("blackShoes")) {shoes = new FRStyleBlackShoes();} else if (name.equals("brownShoes")) {shoes = new FRStyleBrownShoes();}else if(name.equals("redShoes")) {shoes = new FRStyleRedShoes();}}shoes.prepare();shoes.packing();return shoes;}}cs 이와 같이 복잡하고 관리 하기 힘든 모습이 됩니다. 지금 코드는 몇 줄 되지 않지만, 나중에 여기에 의존 하는 객체가 더 수백개로 늘어나고, 나중에 수정해야 할 일이 생긴다면 정말 생각만 해도 끔찍하겠죠.
구두를 만드는 스토어 객체는 '고수준 구성요소' 입니다. 고수준 구성요소는 다른 저수준 구성 요소에 의해 정의되는 행동이 들어 있는 구성 요소 입니다.
스토어 객체는 구두 객체들을 가지고 있으면서, 이 객체들을 사용해서 구두를 준비하고, 포장하게 됩니다.
이때 스토어 객체는 고수준 구성요소라고 하고, 구두 객체들을 저 수준의 구성요소 라고 합니다. 고수준의 구성요소(스토어)는 저수준 구성요소(구두들)를 가지고 무언가를 할 수 있는 것이죠.
위에 있는 다이어그램의 의미 하는 것은, 고수준의 구성요소가 저수준의 구성요소에 심하게 의존 한다는 것입니다.
이렇게 되면 나중에 새로운 구두가 추가 되면, 스토어 객체 까지 손봐야 할 일이 생긴다는 것이죠.
새로운 구두가 나왔다고, 구두 매장에 오직 새로운 구두 만을 위한 진열대를 가져다 놓거나, 보관함을 준비해야 하면 얼마나 낭비가 심할까요. 만약 이런 새로운 구두의 종류가 새로 추가 될 때 마다 이런 일이 반복된다면? 구두 매장은 복잡해지고, 대체 정체가 무엇인지 알 수 없는 곳이 되어 버릴 지도 모릅니다.
그래서, 우리는 이 의존성을 뒤집어 버려야 합니다.
의존성 뒤집기 원칙
의존성 뒤집기 원칙 = 추상화 된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다.
이 원칙을 제대로 적용하려면, 구상 클래스처럼 구체적인 것이 아니라 추상 클래스나 인터페이스 같이 추상적인 것에 의존 하는 코드를 만들어야 합니다. 그리고 이것은 고수준 모듈과 저수준 모듈에 모두 적용됩니다.
자, 그럼 방금 말했던 구두 가게에 이 원칙을 적용해 볼까요?
왜 구두가게에 이런 일이 일어나나요? 바로 구두라는 추상적인 개념이 없어서 그런 것이죠. 이 구두 가게에서 판매 되는 구두는 무엇이다 라는 공통적이고 추상적인 개념을 정해 놓으면, 앞으로 생산 되는 구두는 이 기준에 맞추어서 만들어 질 것 입니다.
예를 들면은, 이 스토어에서 구두는 200mm~320mm 사이이며 가죽으로 만들어 지는 수제 구두 이다. 라고 정해 놓는 다면, 길이가 400mm이고 천으로 만들어 지는 왕발 구두 같은 요상한 제품은 나오지 않을 것이라는 말이죠.
이렇게 하면 고수준 모듈 (ShoesStore)과 저수준 모듈(구두 객체들) 모두 추상클래스인 Shoes에 의존 하게 됩니다.
의존성 뒤집기 원칙을 지키는데 도움이 될만한 가이드 라인
1. 어떤 변수라도 구상 클래스에 대한 레퍼런스를 저장하지 말것
(new 연산자 사용 하면 구상 클래스 레퍼런스를 저장하는 것, 이것 대신 팩토리를 사용하라!)
2. 구상 클래스에서 유도된 클래스를 만들지 말 것
(구상 클래스에서 유도 된 클래스를 만들면 특정 구상 클래스에 의존 하게 된다)
3. 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 말 것.
(이미 구현되어 있는 메소드를 오버라이드 하는 것은, 애초부터 베이스 클래스가 잘 추상화 되어 있는 것이 아님!!! 베이스 클래스에서 메소드를 정의 할때는 모든 서브클래스에서 공유할 수 있는 것들만 정의 해야 함.)
---> 이 가이드 라인들은 지향하는 것이지, 꼭 지켜져야 하는 것은 아님. 자바 프로그램 가운데 이것을 지키는 것은 거의 없음. 알고 있느냐, 아니냐가 중요한 문제이다.
원재료군으로 나누는 추상 팩토리 패턴
다시 구두 가게로 돌아와서, 입소문을 탄 우리 수제화 매장이 더 많은 나라에 진출을 했습니다! 인도, 이태리, 중국 등등 많은 곳에 매장이 생겼죠. 팩토리 메소드 패턴을 이용해 프레임워크를 잘 잡아 놓았기 때문에, 나라별로 같은 서비스를 제공 할 수 있게 되었습니다.
하지만 어제 들어온 보고서에 의하면, 몇몇 분점에서는 밑창을 조금 더 싼 밑창으로 바꿔서 넣거나, 다른 자잘한 재료들을 바꿔서 마진을 올리고 있다고 합니다. 그래서 무언가 조치를 취하기로 했죠.
그래서 내놓은 방안이 원재료를 생산하는 공장을 만들고 분점으로 배송하는 정책입니다.
그런데 문제가 있는게, 지난 편에서 보았듯이, 같은 검은 구두라고 하더라도, 일본매장의 검은 구두에는, 고무로 된 밑창이 들어가고 프랑스 매장의 검은 구두의 밑창은 플라스틱과 혼합된 형태입니다.
매장별로 들어가는 같은 제품이라 하더라도 재료가 다르죠. 이걸 어떻게 해결할까요?
지역 별로 소규모 재료 공장을 나누어 만들면 되지 않을 까요?
한 번 그렇게 해 봅시다.
123456789interface ShoesIngredientFactory {public Bottom makeBottom();public Leather makeLeather();public boolean hasPattern();}cs 공통 기능을 제공할 신발 원재료 공장을 정의해 줍니다.
123456789101112131415161718192021222324252627282930313233343536373839404142434445//일본 매장으로 가는 재료 공장class JPShoesIngredientFactory implements ShoesIngredientFactory {@Overridepublic Bottom makeBottom() {// TODO Auto-generated method stubreturn new RubberBottom();}@Overridepublic Leather makeLeather() {// TODO Auto-generated method stubreturn new LeatherOfCows();}@Overridepublic boolean hasPattern() {// TODO Auto-generated method stubreturn false;}}//프랑스 매장으로 가는 재료 공장class FRShoesIngredientFactory implements ShoesIngredientFactory {@Overridepublic Bottom makeBottom() {// TODO Auto-generated method stubreturn new PlasticAndRubberBottom();}@Overridepublic Leather makeLeather() {// TODO Auto-generated method stubreturn new LeatherOfSheeps();}@Overridepublic boolean hasPattern() {// TODO Auto-generated method stubreturn true;}}cs 원재료 공장 인터페이스를 구현하는 구현 클래스를 만들구요
12345678910111213141516171819202122232425262728293031323334353637383940414243//고무 밑창class RubberBottom implements Bottom {@Overridepublic String getName() {// TODO Auto-generated method stubreturn "고무";}}//플라스틱과 고무 혼합class PlasticAndRubberBottom implements Bottom {@Overridepublic String getName() {// TODO Auto-generated method stubreturn "플라스틱과 고무의 혼합";}}//소가죽class LeatherOfCows implements Leather {@Overridepublic String getName() {// TODO Auto-generated method stubreturn "소가죽";}}//양가죽class LeatherOfSheeps implements Leather {@Overridepublic String getName() {// TODO Auto-generated method stubreturn "양가죽";}}cs 재료들을 구현 하는 클래스 들을 만듭니다.
위의 재료들이 구현하는 인터페이스는 아래와 같습니다.
123456789101112interface Bottom {public String getName();}interface Leather {public String getName();}cs 이제 Shoes 추상 클래스를 살펴 봅시다.
123456789101112131415161718192021222324252627282930abstract class Shoes {String name;Bottom bottom;Leather leather;boolean hasPattern;abstract void assembling();void prepare() {System.out.println("완성된 신발을 준비 중 입니다..");}void packing() {System.out.println("신발을 포장 하고 있습니다..");}public String getName() {return name;}public void setName(String name) {this.name = name;}}cs 원재료 들을 조립하는 assembling 이라는 추상 메소드가 존재 합니다.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364class BlackShoes extends Shoes {ShoesIngredientFactory shoesIngredientFactory;public BlackShoes(factory_abstract_factory.ShoesIngredientFactory shoesIngredientFactory) {this.shoesIngredientFactory = shoesIngredientFactory;}@Overridevoid assembling() {// TODO Auto-generated method stubSystem.out.println("신발을 만들고 있습니다.. " + name);leather = shoesIngredientFactory.makeLeather();bottom = shoesIngredientFactory.makeBottom();System.out.println("신발 정보 : 밑창은 " + bottom.getName() + " 사용 하였으며 가죽은 " + leather.getName() + " 사용하였음");}}class BrownShoes extends Shoes {ShoesIngredientFactory shoesIngredientFactory;public BrownShoes(factory_abstract_factory.ShoesIngredientFactory shoesIngredientFactory) {this.shoesIngredientFactory = shoesIngredientFactory;}@Overridevoid assembling() {// TODO Auto-generated method stubSystem.out.println("신발을 만들고 있습니다.. " + name);leather = shoesIngredientFactory.makeLeather();bottom = shoesIngredientFactory.makeBottom();System.out.println("신발 정보 : 밑창은 " + bottom.getName() + " 사용 하였으며 가죽은 " + leather.getName() + " 사용하였음");}}class RedShoes extends Shoes {ShoesIngredientFactory shoesIngredientFactory;public RedShoes(factory_abstract_factory.ShoesIngredientFactory shoesIngredientFactory) {this.shoesIngredientFactory = shoesIngredientFactory;}@Overridevoid assembling() {// TODO Auto-generated method stubSystem.out.println("신발을 만들고 있습니다.. " + name);leather = shoesIngredientFactory.makeLeather();bottom = shoesIngredientFactory.makeBottom();System.out.println("신발 정보 : 밑창은 " + bottom.getName() + " 사용 하였으며 가죽은 " + leather.getName() + " 사용하였음");}}cs 구두 인터페이스를 구현 하는 구두 클래스 입니다. ShoesIngredientFactory의 인스턴스를 받아서 여기서 원재료를 직접 받게 됩니다. assembling 메소드를 보시면 가죽과 밑창을 각각 공장에서 받아 조립하고 있습니다.
여기에서 구두 클래스는 전혀 신경을 쓰지 않습니다. 신발을 만드는 방법만 알고 있을 뿐이니까요. 어떤 지역의 팩토리를 사용하든 구두 클래스는 언제든 재활용 할 수 있습니다.
자 이제 마지막으로 고객에게 주문을 받을 수 있는 Store 객체를 만들어 보겠습니다.
123456789101112131415161718abstract class ShoesStore {public Shoes orderShoes(String name) {Shoes shoes;shoes = makeShoes(name);shoes.assembling();shoes.prepare();shoes.packing();return shoes;}abstract Shoes makeShoes(String name);}cs 일본 매장과 프랑스 매장을 만들어 봅시다.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657class JPShoesStore extends ShoesStore {@OverrideShoes makeShoes(String name) {// TODO Auto-generated method stubShoes shoes = null;ShoesIngredientFactory shoesIngredientFactory = new JPShoesIngredientFactory();if (name.equals("blackShoes")) {shoes = new BlackShoes(shoesIngredientFactory);shoes.setName("일본 스타일의 검은 구두");} else if (name.equals("brownShoes")) {shoes = new BrownShoes(shoesIngredientFactory);shoes.setName("일본 스타일의 갈색 구두");} else if (name.equals("redShoes")) {shoes = new RedShoes(shoesIngredientFactory);shoes.setName("일본 스타일의 빨간 구두");}return shoes;}}class FRShoesStore extends ShoesStore {@OverrideShoes makeShoes(String name) {// TODO Auto-generated method stubShoes shoes = null;ShoesIngredientFactory shoesIngredientFactory = new FRShoesIngredientFactory();if (name.equals("blackShoes")) {shoes = new BlackShoes(shoesIngredientFactory);shoes.setName("프랑스 스타일의 검은 구두");} else if (name.equals("brownShoes")) {shoes = new BrownShoes(shoesIngredientFactory);shoes.setName("프랑스 스타일의 갈색 구두");} else if (name.equals("redShoes")) {shoes = new RedShoes(shoesIngredientFactory);shoes.setName("프랑스 스타일의 빨간 구두");}return shoes;}}cs 주문이 들어온 구두를 원재료 공장에서 재료를 받아서 만들 준비를 합니다. (구두 재료 팩토리 인스턴스를 보냄) 그리고 매장에서 받은 재료를 가지고 조합 해서 작업을 마무리 하는 것이죠.
실제 주문을 하는 과정을 살펴 봅시다.
12345678910111213public class ShoesDrive {public static void main(String[] args) {JPShoesStore jpStore = new JPShoesStore();jpStore.orderShoes("blackShoes");FRShoesStore frStore = new FRShoesStore();frStore.orderShoes("redShoes");}}cs 누군가 일본 매장과 프랑스 매장으로 가서 구두를 주문합니다.
일본 매장에서 검은 구두를 주문하면, 매장에서는 주문을 받고 (orderShoes)
주문을 받은 구두장이는 일본 매장을 담당하는 원 재료 공장에 알맞는 재료를 요청합니다. (makingShoes)
그럼 원재료 공장이 가동되고, 알맞는 구두의 재료가 제작됩니다.
그리고 이 재료들을 가지고 구두장이가 조합을 해서 구두를 만듭니다.
그리고 포장을 해서 고객들에게 보내지게 됩니다.
결과적으로, 추상 팩토리 패턴을 사용하면 객체들 간의 결합이 느슨해져서 유지 보수에 유용하게 사용 될 수 있습니다.
'IT, 프로그래밍 > Design Patterns' 카테고리의 다른 글
[디자인 패턴] 커맨드 패턴 (Command Pattern) (1) 2017.12.25 [디자인패턴]싱글턴 패턴(Singleton Pattern) (0) 2017.12.25 [디자인패턴] 팩토리 메소드 패턴 (Factory-Method Pattern) (2) 2017.12.10 [디자인패턴] 데코레이터 패턴 (Decorator Pattern) (4) 2017.10.21 [디자인패턴] 옵저버 패턴 (Observer Pattern) - Java 내장 객체 사용 (2) 2017.10.04