Tips/디자인 패턴 (Design Pattern)

디자인 패턴 강좌 - Abstract Factory

dextto™ 2012. 9. 22. 11:44

이번 강좌에서는 GoF의 분류에 따라 생성(Creational) 디자인 패턴 중 하나인 Abstract Factory 패턴을 소개합니다.


Factory Method 패턴에서는 상속을 이용하여 객체 생성을 파생클래스에게 위임하여 실제 생성되는 객체는 파생 클래스가 결정하도록 하였습니다. 

이와 유사한 디자인 패턴으로 Abstract Factory 패턴이 있습니다.
Abstract Factory 패턴은 Factory와 Product를 추상화 시켜 Abstract 클래스를 선언하고,
실제 구현은 그 하위 클래스에게 위임하도록 합니다.

예를 들어 다음과 같은 요구사항을 구현해 봅시다.
 

아이스크림을 만드는 공장(IcecreamFactory)이 있습니다.

이 공장에는 딱딱한 하드(Hard)를 만드는 라인과 부드러운(Soft) 아이스크림을 만드는 라인이 있습니다. (여름엔 하드가 시원하고 맛있네요 :) )

각 제품군에는 어떤 재료(Chocolate, Banana)를 사용하는 냐에 따라 실제 제품이 결정됩니다.


먼저 패턴을 적용하지 않고 위 요구사항을 만족하도록 각 제품을 생성하도록 클래스를 작성해 봅시다.

Main.java import icecream.*; public class Main { public static void main(String[] args) { createAllHardIcecreams(); createAllSoftIcecreams(); } private static void createAllHardIcecreams() { ChocolateHard ch = new ChocolateHard(); ch.make(); BananaHard bh = new BananaHard(); bh.make(); } private static void createAllSoftIcecreams() { ChocolateSoft cs = new ChocolateSoft(); cs.make(); BananaSoft bs = new BananaSoft(); bs.make(); } } ChocolateHard.java package icecream; public class ChocolateHard { public void make() { System.out.println("ChocolateHard를 만듭니다."); } } ChocolateSoft.java package icecream; public class ChocolateSoft { public void make() { System.out.println("ChocolateSoft를 만듭니다."); } } BananaHard.java package icecream; public class BananaHard { public void make() { System.out.println("BananaHard를 만듭니다."); } } BananaSoft.java package icecream; public class BananaSoft { public void make() { System.out.println("BananaSoft를 만듭니다."); } }



 

뭔가 중복된 구조가 보이시나요?
먼저 Hard와 Soft 아이스크림을 반복해서 만들고 있고,
Hard/Soft에 대해 원재료가 뭐냐에 따라 또 세부적으로 제품이 만들어 지는 군요.

이 코드를 다듬어서 다음과 같은 구조를 가지도록 리팩토링을 해 봅시다.
Factory Method와 같이 각 클래스를 직접 생성하지 않고 인터페이스만 Main클래스에 노출시키도록 합시다.


 

사실 hardfactory, softfactory 이렇게 물리적으로 패키지가 나누어져 있기 때문에
각 패키지 내의 클래스명에는 Hard/Soft 이런 단어를 안 붙이는 게 맞지만 이해를 돕기위해 중복해서 붙여놓았습니다.

먼저 IcreamFactory클래스는 Abstract 클래스로서, 세부 Factory 클래스에서 어떤 일을 수행하는 지를 선언합니다.
실제로 어떤 Factory객체가 생성될 지는 getFactory클래스의 인자에 따라 달라집니다.
이렇게 Client(즉, Main클래스)에서 인자로 클래스명을 넘겨 사용하고자 하는 Factory객체를 생성하는 방법도 많이 사용됩니다.
getFactory함수를 사용하지 않는다면 다음과 같이 HardFactory와 SoftFactory클래스에 각각 createFactory함수를 static으로 선언해야 겠지요.

public static getHardFactory() { return new HardFactory(); }
일단 어떤 제품군을 생성할 지에 대한 Factory 객체를 얻었다면,
해당 공장에서 생산하는 제품을 얻어야 합니다.
공장에서 생산하는 제품은 인터페이스로 정의되어 있습니다.

public abstract ChocolateIcecream createChocolateIcecream(); public abstract BananaIcecream createBananaIcecream();
실제 제품은 공장 제품군에 맞게 각각 구현이 됩니다.
즉, Hard를 만드는 공장에서는 Hard 제품을, Soft 아이스크림을 만드는 공장에서는 Soft한 제품을 만들지요.

IcecreamFactory.java package hardfactory; import icecreamfactory.*; public class HardFactory extends IcecreamFactory { @Override public BananaIcecream createBananaIcecream() { return new BananaHard(); } @Override public ChocolateIcecream createChocolateIcecream() { return new ChocolateHard(); } } SoftFactory.java package softfactory; import icecreamfactory.*; public class SoftFactory extends IcecreamFactory { @Override public BananaIcecream createBananaIcecream() { return new BananaSoft(); } @Override public ChocolateIcecream createChocolateIcecream() { return new ChocolateSoft(); } }
실제 생성되는 제품인 BananaHard, ChocolateHard, BananaSoft, ChocolateSoft 객체는
그 재료가 무엇이냐에 따라 만들어 지는 과정이 다르기 때문에,
상위 객체(BananaIcecream, ChocolateIcecream)에서 제공한 인터페이스를 적당하게 구현하고 있습니다.

BananaIcecream.java package icecreamfactory; public abstract class BananaIcecream { public BananaIcecream() { mashBanana(); mixBanana(); } public abstract void mashBanana(); public abstract void mixBanana(); }
그럼 BananaHard 클래스를 볼까요?

BananaHard.java package hardfactory; import icecreamfactory.BananaIcecream; public class BananaHard extends BananaIcecream { @Override public void mashBanana() { System.out.println("Hard: mashBanana"); } @Override public void mixBanana() { System.out.println("Hard: mixBanana"); } }

전체 소스는 첨부 파일을 참고하세요.
이렇게 Abstract Factory 패턴을 적용하고 나면 Main 클래스에서는 다음과 같이 바뀔 겁니다.

Main.java import icecreamfactory.*; public class Main { public static void main(String[] args) { createAllHardIcecreams(); createAllSoftIcecreams(); } private static void createAllHardIcecreams() { IcecreamFactory factory = IcecreamFactory.getFactory("hardfactory.HardFactory"); factory.createChocolateIcecream(); factory.createBananaIcecream(); } private static void createAllSoftIcecreams() { IcecreamFactory factory = IcecreamFactory.getFactory("softfactory.SoftFactory"); factory.createChocolateIcecream(); factory.createBananaIcecream(); } }
마지막으로 Abstract Meothod 패턴이 사용되는 상황을 다시 정리해 보겠습니다.

-. 생성/구성/표현되는 방식과 무관하게 시스템을 독립적으로 만들고자 할 때

-. 제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 표현하고 싶을 때


Abstract Meothod 패턴은 제품군을 쉽게 다른 것으로 대체할 수 있는 장점이 있지만,
새로운 종류의 제품군을 추가하기 어렵습니다.

예를 들어 딸기 맛이 나는 아이스크림을 추가하려고 하면 기존의 클래스를 많이 손봐야 합니다.

-. IcecreamFactory에 createStrawberryIcecream 인터페이스 추가

-. HardFactory, SoftFactory에 인터페이스 구현부 추가

-. hardfactory, softfactory 패키지에 StrawberryHard, StrawberrySoft 클래스 추가


따라서 제품군이 어느 정도 정해져 있고 잘 바뀌지 않는 상황에서 적용해야 추후 제품이 추가될 경우 코드를 수정하는 일이 줄어들겠습니다. :)

반응형