Tips/디자인 패턴 (Design Pattern)

디자인 패턴 강좌 - Factory Method

dextto™ 2012. 9. 15. 11:42

GoF의 분류에 따라 생성(Creational) 디자인 패턴 중 하나인 Factory Method 패턴을 소개합니다.


다음과 같이 문제상황이 주어졌습니다. 

사용자 이름을 멤버 변수로 가진 CreditCard클래스가 있고, CreditCard 객체가 사용될 때 어떤 operation(use())을 하고자 합니다.


처음에는 이렇게 간단히 구현할 겁니다.

Main.java
public class Main { public static void main(String[] args) { CreditCard card = new CreditCard("홍길동"); card.use(); } }

CreditCard.java
public class CreditCard { public static final String USED_MSG = "의 CreditCard를 사용합니다."; private String owner; public CreditCard(String owner) { System.out.println(owner + "의 CreditCard를 만듭니다."); this.owner = owner; } public void use() { System.out.println(owner + USED_MSG); } }

하지만 이후 카드 회사에서는 (연회비는 비싸지만) 보너스 포인트를 추가로 쌓아주는 

BonusCard를 발급하기로 했습니다.


이럴 경우는 Main.java 파일을 다음과 같이 수정해야 합니다. 왜냐하면 Main클래스와 이 클래스에서 객체를 생성하는 클래스는 강한 연관을 가지고 있기 때문입니다.

public class Main { public static void main(String[] args) { //CreditCard card = new CreditCard("홍길동"); BonusCard card = new BonusCard("나잘난"); card.use(); } }

그러면 이를 좀 더 객체지향적으로 바꾸어서 Main클래스에서 내부 객체 생성이 어떻게 되는 지 알 필요가 없이 인터페이스만 보고 사용할 수 있도록 바꾸려면 어떻게 해야 될까요?

다음과 같이 객체 생성 인터페이스를 외부에 공개하고 실제 객체가 생성되는 부분은 안으로 감추어서 구현하면 될 것입니다.

이 예제에서의 Factory Method 패턴은 사실 Template Method 패턴을 두 번 적용한 것입니다.
위키피디아에서는 그냥 객체 생성방법에 초점을 맞추기 위해 Product에는 Template Method를 적용하지 않았네요.

어쨋든 Factory Method 패턴과 Template Method 패턴은 거의 동일합니다.
- 행위 패턴인 Template Method 패턴은 중복을 없애고 코드 재사용을 위해 객체를 사용하는 방법에 초점을 맞추고 있는 반면, 
- Factory Method 패턴은  생성하고자 하는 객체의 클래스(Product)와 이를 생성하는 클래스(Factory)의 인터페이스만 공개하여 재사용성을 높입니다.

이 패턴의 구현은 다음과 같이 됩니다.

Main.java
public
class Main { public static void main(String[] args) { Factory factory = new CardFactory(); Product card1 = factory.create("홍길동"); Product card2 = factory.create("나잘난"); card1.use(); card2.use(); } }

Factory.java
package framework; public abstract class Factory { public final Product create(String owner) { Product p = createProduct(owner); return p; } protected abstract Product createProduct(String owner); }

Product.java
package
framework; public abstract class Product { public abstract void use(); }
 

CardFactory.java
package card; import framework.Factory; import framework.Product; public class CardFactory extends Factory { protected Product createProduct(String owner) { //return new CreditCard(owner); return new BonusCard(owner); } }

CreditCard.java
package card; import framework.Product; public class CreditCard extends Product { public static final String USED_MSG = "의 CreditCard사용합니다."; private String owner; public CreditCard(String owner) { System.out.println(owner + "의 CreditCard를 만듭니다."); this.owner = owner; } public void use() { System.out.println(owner + USED_MSG); } }

Main 클래스는 생성하고자 하는 XXXProduct를 직접 new로 생성하지 않고, XXXFactory의
createProduct() 인터페이스를 이용하여 생성합니다.
여기서 패키지가 물리적으로 framework와 card로 나누어져 있다는 것에 주목하길 바랍니다.
카드 객체가 필요한 쪽에서는 card 패키지의 내부에 있는 클래스를 세세히 몰라도 됩니다. 즉, Main 클래스와 약한 연관을 가집니다. 
framework 패키지 내의 Factory클래스를 상속받은 XXXFactory클래스를 이용하여 Factory 클래스에 공개된 인터페이스만 이용하면 객체를 생성할 수 있습니다.
실제로 어떤 객체가 생성되는 지는 XXXFactory에서 결정하고 있습니다.

이렇게 패키지를 물리적으로 나누어 놓으면 추가로 다른 Factory와 Product를 추가할 때에도 용이합니다. 예를 들어 Product를 상속받은 Robot 객체가 필요하고, 이 객체를 만들어 사용(use())하고자 한다면,
- robot 패키지를 하나 만들고,
- Product를 상속받은 Robot 클래스
- Factory를 상속받은 RobotFactory 클래스를 만들어 구현하면 됩니다.

물리적으로 완전히 분리되어 있기 때문에 해당 소스를 형상관리 할 때도 편리합니다. 즉, 필요한 소스를 추가/삭제/변경하고 이력관리를 용이하게 할 수 있다는 말이지요. 

Factory Method를 사용하는 상황을 다시 정리해 보겠습니다.

의도
객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 객체를 생성할 지에 대해서는 하위 클래스에서 결정 ==> 클래스 상속을 이용함

활용 예
-. 생성할 객체 타입을 예측할 수 없을 때 ==> 부모 클래스 타입을 이용
-. 생성할 객체의 명세를 하위 클래스에서 정의하고자 하는 경우
-. 객체 생성의 책임을 하위 클래스에 위임시키고 어느 하위 클래스가 위임했는지에 대한 정보를 은닉하고자 하는 경우 

반응형