본문 바로가기

OOP, FP/디자인패턴

헤드퍼스트 디자인 패턴: 4-2. 추상 팩토리 패턴


지난 포스팅에서는 객체 생성을 캡슐화하여 서브클래스에게 위임하는 팩토리 메소드 패턴에 대해 알아보았다.

이번에는 객체 생성을 추상 인터페이스를 통해 캡슐화하여 일련의 제품들을 공급할 수 있는 추상 팩토리 패턴에 대해 복습한다.


그전에 의존성 뒤집기 원칙에 대해서 다시 한번 짚고가자.


의존성 뒤집기 원칙

"추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다."


구상 클래스가 아닌 인터페이스에 맞춰 프로그래밍하라는 세번째 원칙과 비슷하다.

하지만 DIP(Dependency Inversion Principle)에서는 추상화를 더 많이 강조한다.


고수준 구성요소가 저수준 구성요소에 의존하면 안된다고 강조한다.

항상 추상화된 요소에 의존하도록 해야한다.


여기서 ConcreateClient는 고수준 구성요소, 클라이언트가 사용하는 유닛들은 저수준 구성요소이다.

저수준 구성요소 하나하나에 일일이 의존하고 있으므로 유닛의 규격이 바뀐다거나 하면 유닛 전부를 수정해야 한다.

또 연관된 일련의 유닛(지상유닛)들을 수정하거나 추가할때도 마찬가지다.



이제 클라이언트는 유닛을 추상화시킨 Unit이라는 인터페이스에 의존하게 되었다.

공통 멤버변수가 필요하다거나 일부 구현(자바8이전)이 필요하면 추상 클래스로 정의하면 되겠다.


자세한 내용은 위의 링크를 참조하자.


추상 팩토리 패턴

인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.

주로 제품군을 만들 때 쓰는 패턴.


추상 팩토리 패턴을 사용하면 클라이언트에서 추상화된 인터페이스를 통해서 일련의 제품들을 제공받을 수 있다.

물론 클라이언트에서는 실제로 어떤 제품이 생산되는지는 전혀 알 필요가 없다.

추상화된 인터페이스로부터 다시 추상화된 제품을 받아서 추상화된 기능만 사용하면 된다.

따라서 클라이언트와 팩토리에서 생산되는 제품을 분리시킬 수 있다.



생각보다 복잡해보이지만 별 거 없다.

모든 구상 팩토리(ConcreateFactory)들이 구현해야 하는 추상 팩토리 인터페이스(AbstractFactory)를 정의한다.

추상 팩토리에서는 일련의 제품을 생산하기 위한 메소드를 정의하고 구상 팩토리는 이를 구현하기만 하면 된다.

ConcreateFactory를 2개로 나눈 이유는 제품이 두 개의 제품군으로 이루어져있기 때문이다.

각 각 다른 일련의 제품군을 생산하기 때문에 구상 팩토리 가운데 적당한 팩토리를 골라서 사용하면된다.



이전포스팅에서 팩토리 메소드 패턴의 예시로 만들어봤던 다이어그램이다.

지상 인간형 유닛을 생산하는 배럭과 공중유닛을 생산하는 스타포트라는 서브클래스를 이용했었다.


여기서 추상 팩토리 패턴을 이용해야 하는 상황은 무엇이 있을까?

정의와 용도를 살펴보면 일련의 제품군을 생산하는데 사용한다고 했다.

우선 제품군은 지상 인간형 유닛과 공중 유닛 두 집합으로 나눌 수 있다.


그런데 책에서 나온 예시는 피자분점마다 피자를 만드는 원재료를 생산하는 팩토리를 예시로 들었었는데....

스타크래프트에는 무언가 재료 여러개를 이용해서 하나의 유닛을 만드는 건물(팩토리)는 존재하지 않는다.

가스와 미네랄로 나눌 수 있긴 하겠지만 가스랑 미네랄 단위를 클래스로 만들긴 무리다.


음... 그냥 내맘대로 상상해보자. 공중형 유닛까지 하면 복잡하므로 지상 인간형 유닛만 마음대로 상상해보자.

지상 인간형 유닛은 옷(화생방보호의, 우주복, 간호복), 무기(총, 화염 방사기, 치료기)라는 재료를 이용해서 만든다.


제품군은 추상 제품인 옷과 무기가 필요하다.

옷은 다시 구상 제품인 화생복, 우주복과 간호복,

무기는 다시 구상 제품인 총, 방사기와 치료기가 필요하다.


팩토리는 옷을 생산하는 팩토리, 무기를 생산하는 팩토리 총 2개가 필요하겠다.



개판이다. 디자인 패턴을 실제로 적용한 경험이 거의 전무해서 그런지 마땅한 예제들이 잘 떠오르지 않는다.

어쨋든 어찌저찌 만든 다이어그램을 살펴보자.


마린팩토리에서는 Spacesuit와 Gun을 생산한다.

파이어벳팩토리에서는 CBR과 FlameThrower을 생산한다.

메딕팩토리에서는 NursingWear와 Curer을 생산한다.


클라이언트에서는 파이어벳을 생산하고 싶으면 유닛의 생산자로 파이어벳 팩토리를 넘겨준다.

이후에는 유닛을 생성하는 코드나 재료를 만드는 메소드에서는 팩토리를 이용해서 옷과 무기를 만들수 있다.


이렇게 각 유닛마다 무기와 옷이 혼동되지 않도록 올바른 규격을 제공하는 것이 가능하고

유연하게 일련의 유닛을 생산할 수 있다.


그럼 마지막으로 팩토리 메소드 패턴과 추상 팩토리 패턴의 유사점과 차이점에 대해 알아보자.

두 패턴 모두 애플리케이션을 특정 구현으로부터 분리시키는 역할을 맡고있다


팩토리 메소드 패턴 VS 추상 팩토리 패턴

(1) 추상 팩토리 패턴 뒤에는 팩토리 메소드 패턴이 숨어있는 것인가?

- 추상 팩토리 패턴에서 생산 메소드가 팩토리 메소드로 구현되는 경우도 종종 있다.

  추상 팩토리가 원래 일련의 제품들을 생산하기 위한 인터페이스를 정의하는 용도니까.

  그러한 추상 팩토리의 인터페이스에 정의되어 있는 메소드는 구상 제품을 생산하는 일을 맡고 있고,

  다시 추상 팩토리의 서브클래스를 만들어서 각 메소드의 구현을 제공한다.

  따라서 추상 팩토리 패턴에서 제품을 생산하기 위한 메소드를 구현하는데 있어서 팩토리 메소드를 사용하는 것은 자연스러운 일이다.


(2) 팩토리 메서드 패턴은 클래스를 써서 제품을 만들고 추상 팩토리 패턴에서는 객체를 써서 제품을 만든다.

- 팩토리 메서드 패턴은 클래스 상속을 통해서 객체를 만들지만

  추상 팩토리 패턴은 객체 구성(Composition)을 통해서 객체를 만든다.


(3) 팩토리 메서드 패턴을 통해 객체를 생성할 때는 클래스를 확장하고 팩토리 메소드를 오버라이드 해야 한다.

- 팩토리 메서드 패턴은 결국 서브클래스를 통해서 객체를 만들기 위한 패턴이므로

  클라이언트에서는 자신이 사용할 추상 형식만 알면 된다. 구상 형식은 서브클래스에서 처리해주기 때문이다.

  즉, 클라이언트와 구상 형식을 분리시켜주는 역할을 맡는다.


(4) 추상 팩토리 패턴에서는 제품군을 만들기 위한 추상 형식(인터페이스) 을 제공한다.

- 제품이 생산되는 방법은 이 형식의 서브클래스(인터페이스 구상 클래스)에서 제공한다.

  팩토리를 이용하고 싶으면 일단 팩토리의 인스턴스를 만든 다음 추상 형식을 써서 만든 코드에 전달하면 된다.

  마찬가지로 클라이언트에서 사용하는 실제 구상 제품이 분리된다.

  (일련의 연관된 제품을 하나로 묶을 수 있는 장점이 있다.)


(4) 추상 팩토리 패턴에서 제품군에 제품을 추가하거나 하는 식으로 제품군을 확대해야 하는 경우, 인터페이스를 바꿔야한다.

- 반면에 추상 팩토리 패턴은 하나의 제품군에 속해있는 여러 제품을 한번에 만들 수 있다.

  팩토리 메소드 패턴에서는 한가지 제품만 생산하기는 하지만 복잡한 인터페이스가 필요하지 않고 메소드도 하나만 있으면 된다.


* 어떤 패턴을 쓰든 결국 객체 생성을 캡슐화해서 애플리케이션의 결합을 느슨하게 만들고,

  특정 구현에 덜 의존하도록 만들 수 있으므로 용도에 따라 적절한 패턴을 사용하면 된다.


* 팩토리 메소드 패턴

- 클라이언트의 코드와 인스턴트를 만들어야할 구상 클래스를 분리시켜야할 때.

- 어떤 구상 클래스를 필요로 하게 될지 미리 알 수 없는 경우


* 추상 팩토리 패턴

- 클라이언트에서 서로 연관된 일련의 제품들을 만들어야 할 때

 

객체 생성을 추상 인터페이스를 통해 캡슐화하여 일련의 제품들을 공급할 수 있는 추상 팩토리 패턴에 대해 복습을 마치겠다.


1. 스트래티지 패턴

2. 옵저버 패턴

3. 데코레이터 패턴

4-1. 팩토리 메서드 패턴

4-2. 추상 팩토리 패턴