new 연산자를 사용한다고 해서 새로운 객체가 만들어지는 것은 아닙니다.
객체의 인스턴스를 만드는 작업이 항상 공개되어야 하는 것은 아니며, 오히려 모든 것을 공개했다가는 결합 문제가 생길 수 있다는 사실을 배웁니다.
팩토리 패턴으로 불필요한 의존성을 없애서 결합 문제를 해결하는 방법을 알아봅시다.
질문
특정 구현을 바탕으로 프로그래밍 하지 않아야 한다는 원칙을 배웠는데, new를 쓸 때마다 결국은 특정 구현을 사용하게 되는 것 아닌가요?
new를 사용하면 구상 클래스의 인스턴스가 만들어집니다. 당연히 인터페이스가 아닌 특정 구현을 사용하는 거죠.
정말 좋은 질문입니다. 앞에서 구상 클래스를 바탕으로 코딩하면 나중에 코드를 수정해야 할 가능성이 커지고, 유연성이 떨어진다고 배웠었죠.
일련의 구상 클래스가 있다면 어쩔 수 없이 다음과 같은 코드를 만들어야 합니다.
// 오리를 나타내는 클래스는 여러가지 있지만 컴파일 하기 전까지는
// 어떤 것의 인스턴스를 만들어야 하는지 알 수 없습니다.
Duck duck;
if (picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}
이 코드를 보면 구성 클래스의 인스턴스가 여러 개 있으며, 그 인스턴스의 형식은 실행 시에 주어진 조건에 따라 결정된다는 사실을 알 수 있습니다.
이런 코드를 변경하거나 확장해야 할 때는 코드를 다시 확인하고 새로운 코드를 추가하거나 기존 코드를 제거해야 합니다.
따라서 코드를 이런 식으로 만들면 관리와 갱신이 어려워지고 오류가 생길 가능성도 커집니다.
사실 new는 문제가 없습니다. 진짜 말썽을 일으키는 녀석은 바로 '변화'입니다.
변화하는 무언가 때문에 new를 조심해서 사용해야 합니다.
인터페이스에 맞춰서 코딩하면 시스템에서 일어날 수 있는 여러 변환에 대응할 수 있습니다. 왜 그럴까요?
인터페이스를 바탕으로 만들어진 코드들은 어떤 클래스든 특정 인터페이스만 구현하면 사용할 수 있기 때문이죠. 이게 다 다형성 때문입니다.
반대로 구상 클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야 하므로 수많은 문제가 생길 수 있습니다.
즉 변경에 닫혀 있는 코드가 되는 거죠. 새로운 구상 형식을 써서 확장해야 할 때는 어떻게서든 다시 열 수 있게 만들어야 합니다.
우리가 배운 첫 번째 디자인 원칙을 떠올려 보세요. 바뀌는 부분을 찾아내서 바뀌지 않는 부분과 분리해야 한다는 원칙, 기억나죠?
orderPizza() 메소드에서 가장 문제가 되는 부분은 인스턴스를 만드는 구상 클래스를 선택하는 부분입니다.
이 부분 때문에 상황이 변하면 코드를 변경해야하니 캡슐화를 할 차례군요
새로 만들 객체를 팩토리라고 부르겠습니다.
'객체 생성을 처리하는 클래스를 팩토리(Factory)라고 부릅니다.
일단 SimplePizzaFactory 클래스를 만들고 나면 orderPizza() 메소드는 새로 만든 객체의 클라이언트가 됩니다. 즉 새로 만든 객체를 호출하는 거죠.
피자가 필요할 때마다 피자 공장에 피자 하나 만들어 달라고 부탁한다고 생각하면 됩니다. 이제 더 이상 orderPizza() 메소드에서 어떤 피자를 만들지 고민하지 않아도 됩니다.
orderPizza() 메소드는 Pizza 인터페이스를 구현하는 피자를 받아서 그 인터페이스에서 정의했던 prepare(), bake(), cut(), box() 메소드를 호출하기만 하면 되죠.
하지만 아직 몇 가지 자질구레한 사항을 해결해야 합니다. 예를 들어 orderPizza() 메소드에서 객체를 생성하는 부분에 썼던 코드 대신 들어갈 코드 같은 것 말이죠.
피자 가게에서 사용할 만한 간단한 팩토리 클래스를 구현한 후 어떻게 할지 생각해 봅시다.
SimplePizzaFactory를 사용하는 클라이언트가 매우 많을 수도 있습니다.
여기에는 orderPizza() 메소드만 있지만, 피자 객체를 받아서 피자를 설명하거나 가격을 알려주는 PizzaShopMenu 클래스와
PizzaStore 클래스와는 조금 다른 방식으로 피자 주문을 처리하는 HomeDelivery 클래스에도 이 팩토리를 사용할 수 있습니다.
그런 상황에서 피자 객체 생성 작업을 팩토리 클래스로 캡슐화해 놓으면 구현을 변경할 때 여기저기 고칠 필요 없이 팩토리 클래스 하나만 고치면 되겠죠.
그리고 조금 있으면 클라이언트 코드에서 구상 클래스의 인스턴스를 만드는 코드를 전부 없애 버릴 겁니다.
간단한 팩토리를 정적 메소드로 정의하는 기법도 많이 쓰입니다. 정적 팩토리(static factory)라고 부르기도 하죠.
왜 정적 메소드를 쓰는지 궁금할 텐데, 객체 생성 메소드를 실행하려고 객체의 인스턴스를 만들지 않아도 되기 때문입니다.
하지만 서브 클래스를 만들어서 객체 생성 메소드의 행동을 변경할 수 없다는 단점이 있다는 점도 꼭 기억해두세요.
간단한 팩토리(Simple Factory)는 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝습니다.
엄연히 말하면 패턴은 아니지만 중요하지 않은 건 아닙니다. 새로 만든 피자 가계의 클래스 다이어그램을 살펴보면서 간단한 팩토리를 왜 쓰는지 알아봅시다.
간단한 팩토리는 일종의 워밍업이라고 생각합니다.
주의
디자인 패턴을 얘기할 때, "인터페이스를 구현한다"라는 표현이 나온다고 해서 항상
"클래스를 선언하는 부분에 implements 키워드를 써서 어떤 자바 인터페이스를 구현하는 클래스를 만든다" 라고 생각하면 안됩니다.
어떤 상위 형식(클래스와 인터페이스)에 있는 구상 클래스는 그 상위 형식의 '인터페이스를 구현하는' 클래스 라고 생각하면 됩니다.
이미 1가지 방법을 앞에서 배웠습니다.
SimplePizzaFactory를 삭제하고, 3가지 서로 다른 팩토리(NYPizzaFactory, ChicagoPizzaFactory, CalifornizaPizzaFactory)를 만든 다음, PizzaStore에서 적당한 팩토리를 사용하도록 하는 방법입니다.
하지만 지점들을 조금 더 제대로 관리해야 합니다.
지점에서 우리가 만든 팩토리로 피자를 만들긴 하는데, 굽는 방식이 달라진다거나 종종 피자를 자르는 것을 까먹는 일이 생기기 시작했습니다.
심지어 이상하게 생긴 피자 상자를 쓰는 일도 있었습니다.
이 문제를 해결하려면 PizzaStore와 피자 제작 코드 전체를 하나로 묶어주는 프레임워크를 만들어야 합니다. 물론 그렇게 만들면서도 유연성은 잃어버리면 안되겠죠.
SimplePizzaFactory를 만들기 전에 썼던 코드에는 피자를 만드는 코드가 PizzaStore와 직접 연결되어 있긴 했지만, 유연성이 전혀 없었습니다.
어떻게 해야 피자 가게와 피자 만드는 과정을 하나로 묶을 수 있을까요?
피자 만드는 일 자체는 전부 PizzaStore 클래스에서 진행하면서도 지점의 스타일을 살릴 수 있는 방법이 있습니ㅏ다.
이제 createPizza() 메소드를 PizzaStore에 다시 넣겠습니다. 하지만 이번에는 그 메소드를 추상 메소드로 선언하고, 지역별 스타일에 맞게 PizzaStore의 서브클래스를 만들겠습니다.
이제 각 지점에 맞는 서브 클래스를 만들어야 합니다. 피자의 스타일은 각 서브클래스에서 결정합니다.
달라지는 점은 createPizzaa() 메소드에 넣고 그 메소드에서 해당 스타일의 피자를 만들도록 할 계획입니다.
그러니 PizzaStore의 서브 클래스에서 createPizza() 메소드를 구현하겠습니다.
이제 PizzaStore 프레임워크에 충실하면서도 각각의 스타일을 제대로 구현할 수 있는 orderPizza() 메소드를 PizzaStore 서브클래스에 구비할 수 있습니다.
public abstract class PizzaStore {
abstract Pizza createPizza(String item); // Pizza 인스턴스를 만드는 일은 이제 팩토리 메소드에서 맡아서 처리합니다.
public Pizza orderPizza(String type) { // 피자 객체 인스턴스를 만드는 일은 PizzaStore의 서브클래스(NYStylePizzaStore, ChicagoStylePizzaStore 등등)에 있는 createPizza() 메소드에서 처리합니다.
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// 1. 에단이 주문한 내역을 따라가 봅시다. 우선 NYPizzaStore가 필요합니다.
PizzaStore nyPizzaStore = new NYPizzaStore();
// 2. 피자 가게가 확보됐으니 이제 주문을 받을 수 있습니다.
nyPizzaStore.orderPizza("cheese");
// 3. orderPizza() 메소드에서 createPizza() 메소드를 호출합니다.
Pizza pizza = createPizza("cheese");
// 4. 아직 준비되지 않은 피자를 받았습니다. 이제 피자를 만드는 작업을 마무리 해야겠죠
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<String>();
void prepare() {
System.out.println("Prepare " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for (String topping : toppings) {
System.out.println(" " + topping);
}
}
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cut the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (String topping : toppings) {
display.append(topping + "\n");
}
return display.toString();
}
}
구상 서브 클래스
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("clam");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("clam");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("pepperoni");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("pepperoni");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("veggie");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("veggie");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
모든 팩토리 패턴은 객체 생성을 캡슐화합니다. 팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화합니다.
팩토리 메소드 패턴(Factory Method Pattern)에서는 객체를 생성할 때 필요한 인터페이스를 만듭니다.
어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 됩니다.
Creator 추상 클래스에서 객체를 만드는 메소드, 즉 팩토리 메소드용 인터페이스를 제공한다는 사실을 알 수 있습니다.
Creator 추상 클래스에 구현되어 있는 다른 메소드는 팩토리 메소드에 의해 생산된 제품으로 필요한 작업을 처리합니다.
하지만 실제 팩토리 메소드를 구현하고 제품(객체 인스턴스)를 만드는 일은 서브 클래스에서만 할 수 있습니다.
"팩토리 메소드 패턴에서는 어떤 클래스의 인스턴스를 만들지를 서브클래스에서 결정한다"라고 하는 얘기를 종종 듣게 될 것입니다. 여기에서 '결정한다'라는 표현을 쓰는 이유는
실행 중에 서브클래스에서 어떤 클래스의 인스턴스를 만들지를 결정해서가 아니라, 생산자 클래스가 실제 생산될 제품을 전혀 모르는 상태로 만들어지기 때문입니다.
사실 더 정확하게 말하면, 사용하는 서브클래스에 따라 생산되는 객체 인스턴스가 결정됩니다.
구상 생산자 클래스가 하나밖에 없더라도 팩토리 메소드 패턴은 충분히 유용합니다.
제품을 생산하는 부분과 사용하는 부분을 분리할 수 있으니까요.
다른 제품을 추가하거나 제품 구성을 변경하더라도 Creator 클래스가 ConcreteProduct와 느슨하게 결합되어 있으므로 Creator는 건드릴 필요가 없죠.
꼭 그래야 하는 건 아닙니다. 몇몇 간단한 구상 제품은 기본 팩토리 메소드를 정의해서 Creator의 서브 클래스 없이 만들 수 있습니다.
앞에서는 매개변수 팩토리 메소드를 사용했습니다. 전달받은 매개변수를 바탕으로 한 가지 이상의 객체를 만들 수 있죠.
하지만 팩토리에서 매개변수를 쓰지 않고 그냥 한 가지 객체만 만드는 경우도 많습니다. 어떤 방법을 쓰던지 팩토리 메소드 패턴을 사용한다는 데는 변함이 없죠
그냥 String을 매개변수로 전달할 뿐이잖아요. 'clam'을 잘못 쳐서 'calm'이라고 치면 어떻게 되죠?
네, 지적한 내용이 맞습니다. 형식 안정성을 조금 더 잘 보장해줄 수 있는 기법이 있습니다.
형식 오류를 컴파일 시에 잡아낼 수 있죠. 예를 들어 매개 변수 형식을 나타내는 객체를 만들 수도 있고, enum을 사용할 수도 있습니다.
팩토리 메소드 패턴에서 피자를 리턴하는 클래스가 서브클래스라는 점을 빼면 거의 같잖아요.
맞습니다. 하지만 간단한 팩토리는 일회용 처방에 불과한 반면, 팩토리 메소드 패턴을 사용하면 여러 번 재사용이 가능한 프레임워크를 만들 수 있습니다.
예를 들어, 팩토리 메소드 패턴의 orderPizza() 메소드는 피자를 만드는 일반적인 프레임워크를 제공합니다. 그 프레임워크는 팩토리 메소드 피자 생성 구상 클래스를 만들었죠.
PizzaStore 클래스의 서브 클래스를 만들 때, 어떤 구상 제품 클래스에서 리턴할 피자를 만들지를 결정합니다.
간단한 팩토리는 객체 생성을 캡슐화하는 방법을 사용하긴 하지만 팩토리 메소드만큼 유연하지는 않습니다. 생성하는 제품을 마음대로 변경할 수 없기 때문이죠.
객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 합니다.
앞쪽에 있던 심하게 의존적인 PizzaStore를 한번 살펴봅시다. 이 코드에서는 모든 피자 객체를 팩토리에 맡겨서 만들지 않고 PizzaStore 클래스 내에서 직접 만들었습니다.
PizzaStore 코드를 다이어그램으로 그려 보면 다음과 같습니다.
구상 클래스 의존성을 줄이면 좋다는 사실은 이제 확실히 알았습니다. 이 내용을 정리해 놓은 객체지향 디자인 원칙이 있습니다.
바로 의존성 뒤집기 원칙(Dependency Inversion Principle, 기존에 의존 역전 원칙이라고 외웠음)입니다.
디자인 원칙 - 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
이 원칙과 "구현보다는 인터페이스에 맞춰서 프로그래밍한다"라는 원칙이 똑같다는 생각이 들 수도 있습니다.
물론 비슷하긴 하지만 의존성 뒤집기 원칙에서는 추상화를 더 많이 강조합니다.
이 원칙에는 고수준 구성 요소가 저수준 구성요소에 의존하면 안되며, 항상 추상화에 의존하게 만들어야 한다는 뜻이 담겨 있습니다. 그런데 이게 대체 무슨 소리일까요?
지금까지 만들어왔던 예제 중 PizzaStore는 고수준 구성 요소라고 할 수 있고, 피자 클래스는 저수준 구성 요소라고 할 수 있습니다.
(고수준 구성 요소는 다른 저수준 구성 요소에 의해 정의되는 행동이 들어있는 구성요소를 뜻합니다.
PizzaStore는 다양한 피자 객체를 만들고, 피자를 준비하고, 굽고 자르고 포장하죠.이때 PizzaStore에서 사용하는 피자 객체는 저수준 구성 요소입니다.)
PizzaStore 클래스는 구상 피자 클래스에 의존하고 있다는 사실을 확실하게 알 수 있습니다.
의존성 뒤집기 원칙에 따르면, 구상 클래스처럼 구체적인 것이 아닌 추상 클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드를 만들어야 합니다.
이 원칙은 고수준 모듈과 저수준 모듈에 모두 적용될 수 있습니다. 이 원칙은 어떻게 적용할 수 있는걸까요?
심하게 의존적인 PizzaStore의 가장 큰 문제점은 PizzaStore가 모든 종류의 피자에 의존한다는 점입니다. orderPizza() 메소드에서 구상 형식의 인스턴스를 직접 만들기 때문이죠.
Pizza라는 추상 클래스를 만들긴 했지만, 이 코드에서 구상 피자 객체를 생성하는 것은 아니기에 추상화로 얻는 것이 별로 없습니다.
어떻게 해야 인스턴스 만드는 부분을 orderPizza()에서 뽑아낼 수 있을까요? 이미 배운대로 팩토리 메소드 패턴으로 인스턴스 만드는 부분을 뽑아낼 수 있습니다.
고수준 모듈과 저수준 모듈이 둘 다 하나의 추상 클래스에 의존하게 됩니다.
이 가이드라인은 항상 지켜야하는 것이 아니라, 우리가 지향해야 할 바를 알려줄 뿐입니다.
하지만 이 가이드라인을 완전히 습득한 상태에서 디자인한다면 원칙을 지키지 않은 부분을 명확하게 파악할 수 있으며, 합리적인 이유로 불가피한 상황에서만 예외를 둘 수 있습니다.
예를 들어, 어떤 클래스가 바뀌지 않는다면 그 클래스의 인스턴스를 만드는 코드를 작성해도 그리 큰 문제가 생기지 않습니다. (ex. String 클래스)
하지만 여러분이 만들고 있는 클래스가 바뀔 수 있다면 팩토리 메소드 패턴을 써서 변경될 수 있는 부분을 캡슐화해야 합니다.
뉴욕과 시카고에서 사용하는 재료는 서로 다릅니다. 최첨단 피자의 명성이 높아져서 캘리포니아 지점을 열었더니, 또 다른 재료를 사용하게 되었습니다.
나중에 다른 곳에 지점을 열면 또 다른 재료를 사용해야 할 겁니다. 이렇게 서로 다른 재료를 제공하려면 원재료군(families of ingredients)를 처리할 방법을 생각해 봐야 합니다.
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}
Pizza 클래스가 팩토리에서 생산한 원재료만 사용하도록 코드를 고쳐봅시다.
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
public String toString() {
...
}
}
기존 코드에서 달라진 점은 원재료를 팩토리에서 바로 가져온다는 점 말고는 없습니다.
팩토리 메소드 패턴을 사용한 코드를 만들었을 때 NYCheesPizza와 ChicagoCheesePiza 클래스를 만들었죠?
그 두 클래스를 살펴보면 지역별로 다른 재료를 사용한다는 것만 빼면 똑같은 형식으로 구성되어 있습니다. 피자를 이루는 기본 요소가 반죽, 소스, 치즈라는 건 마찬가지니까요.
야채 피자와 조개 피자도 마찬가지입니다. 재료만 다를 뿐 준비 단계는 똑같습니다.
따라서 피자마다 클래스를 지역별로 따로 만들 필요가 없습니다. 지역별로 다른 점은 원재료 팩토리에서 처리합니다.
치즈 피자 코드는 다음과 같이 만들 수 있습니다.
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
코드가 꽤 여러번 바뀌었습니다.
추상 팩토리라고 부르는 새로운 형식의 팩토리를 도입해서 피자 종류에 맞는 원재료 군을 생산하는 방법을 구축했습니다.
추상 팩토리로 제품군을 생성하는 인터페이스를 제공할 수 있습니다.
이 인터페이스를 사용하면 코드와 제품을 생산하는 팩토리를 분리할 수 있습니다.
이렇게 함으로써 지역, 운영체제, 룩앤필 등 서로 다른 상황에 맞는 제품을 생산하는 팩토리를 구현할 수 있습니다.
코드가 실제 제품과 분리되어 있으므로 다른 결과가 필요하면 다른 팩토리를 사용하면 됩니다.
추상 팩토리 패턴(Abstract Factory Pattern)은 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공합니다.
구상 클래스는 서브 클래스에서 만듭니다.
이 패턴을 사용하면 클라이언트에서 추상 인터페이스로 일련의 제품을 공급받을 수 있습니다. 이때, 실제로 어떤 제품이 생산되는지는 전혀 알 필요가 없습니다.
따라서 클라이언트와 팩토리에서 생산되는 제품을 분리할 수 있습니다. 클래스 다이어그램을 보면서 어떤 식으로 돌아가는지 알아봅시다.
추상 팩토리 패턴에서 메소드가 팩토리 메소드로 구현되는 경우도 종종 있습니다.
추상 팩토리가 원래 일련의 제품을 만드는 데 쓰이는 인터페이스를 정의하려고 만들어진 것이니 어찌보면 당연합니다.
그 인터페이스에 있는 각 메소드는 구상 제품을 생산하는 일을 맡고, 추상 팩토리의 서브클래스를 만들어서 각 메소드의 구현을 제공합니다.
따라서 추상 팩토리 패턴에서 제품을 생산하는 메소드를 구현하는 데 있어서 팩토리 메소드를 사용하는 건 너무나도 저연스러운 일이라고 할 수 있습니다.
객체지향 원칙
객체지향 패턴 - 추상 팩토리 패턴
객체지향 패턴 - 팩토리 메소드 패턴
객체를 생성할 때 필요한 인터페이스를 만듭니다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메소드를 사용하면 인스턴스 만드는 일을 서브클래스에 맡길 수 있습니다.