Java 기준으로 객체를 생성할 때는 new 연산자를 통해서 Class Template 을 사용하여 Object Instance 를 찍어낸다. Interface 역할체를 생성할 때는 다음과 같은 연산을 통해서 구현체를 지정하기도 한다.
Pizza a = new CheesePizza();
Pizza b = new PepperoniPizza();
이와 같은 방식은 해당 로직이 있는 클래스에서 Concrete 구현체인 CheesePizza 와 PepperoniPizza 에 대한 의존성이 발생하게 된다. 만약 의존성 없이 이를 해결할 수 있다면, 유지 보수에 훨씬 용이할 것이다. 다음과 같이 Type 을 받아서 생성만을 전담하는 Class 를 만들어보자.
public class SimplePizzaFactory {
public Pizza createPizza(){
Pizza pizza;
if(type.equals("cheese")){
pizza = new CheesePizza();
} else if(type.equals("pepperoni")){
pizza = new PepperoniPizza();
} else if(...){
// ...
} else {
// ...
}
return pizza;
}
}
이렇게 한다면 Pizza 를 필요로 하는 객체는 SimplePizzaFactory 만을 의존하여 createPizza() 를 사용할 수 있고, 이 때 다양한 구현체의 Pizza 를 Run-Time 시 생성할 수 있다. 이를 Simple Factory 이라고 한다.
이와 같이 Client 가 Instance 를 생성함에 있어서 의존성을 갖지 않게 하여 생성 로직을 위임하는 것을 Factory Pattern 이라고 하며, 대표적인 두 가지 Pattern 에 대해 알아보자 (Simple Factory 는 공식 Pattern 은 아닌데, 생성을 하는 곳과 생성 함수를 가진 곳이 분리되어 있기 때문이다).
Factory Method Pattern
팩토리 메소드 패턴은 위에서 알아본 바와 같이 Object Create 을 목적으로 하고, 해당 역할을 하는 함수를 노출시킨다. 그리고 Sub Class 들이 이를 사용할 수 있도록 한다 (즉, Inheritance 관계이다). Sub Class 가 어떤 객체를 만들지 결정해서 사용하는 것이다.
Simplefactory 에서는 Client 가 생성의 모든 것을 Factory Class 에만 의지했다면, Factory Method Class 는 생성할 Object 를 필요로 하는 곳과 생성하는 Method 를 합쳐서 Template 형태로 제공해준다는 점에서 차이가 있다.
/*
생성 함수를 가진 곳과 생성을 하는 곳을 합친다
> 그리고 이 합친 곳을 abstract 로 하여, extend 받을 수 있게 한다
*/
public abstract class PizzaStore {
// 이 함수는 그렇게 중요한건 아니다
// 현재 객체가 하는 논리적인 일에 각 구현체들이 할 생성일을 얹은 것
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
return pizza;
}
/*
생성을 직접적으로 하는 부분을 노출시켜서
Sub Class 들이 원하는대로 Pizza 를 생성할 수 있는 틀을 마련해준다
*/
protected abstract Pizza createPizza(String type);
}
/*
생성을 필요로 하는 곳이 원하는 객체를 직접 제어할 수 있도록 한다
SimpleFactory 에서는 Client 가 원하는 것을 제어할 수 없고 Method 가 있는 Factory Class 에만 의존해야 했다
*/
public class ChicagoPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza;
// 해당 Factory Impl 에서 필요한 객체들이 뭔지 직접 제어할 수 있다
if (type.equals("cheese")) {
pizza = new ChicagoCheesePizza();
} else if (type.equals("veggie")) {
pizza = new ChicagoVeggiePizza();
} else {
pizza = null;
}
return pizza;
}
}
일종의 작은 Framework 이라고 보면 된다. 게임 같은 경우에도 게임 캐릭터를 만들어주는 Factory Framework 이 있어도, 어떤 게임 캐릭터를 만들지는 제작자인 내가 직접 제어해야한다. Client 가 원하는 캐릭터를 Factory 제공자에서 알 수 없고 Client 만이 알 수 있기 때문이다.
1. Factory Method Pattern 은 Object 를 생성하는 interface 를 제공하되, 어떤 것을 생성할지에 대해서는 Sub-Class 가 결정할 수 있도록 마련된다
2. Factory Method 는 Object Instantiation 을 Sub Class 로 양보한다고 한다
Factory Method Pattern 은 5가지 SOLID 중 DIP 를 잘 반영한 패턴이라고 볼 수 있다. Pizza 라는 객체 역할체를 생성하기 위해서 Client 인 PizzaStore 는 내부적으로 Pizza 의 종류가 등장하지 않기 때문이다 (물론 팩토리 구현체에는 등장해야 한다).
Abstract Factory Pattern
Abstract Factory Pattern 은 Object Creation 을 하는 과정을 Inheritance 가 아닌 Composition & Delegation 으로 다른 객체들에게 위임하는 차이가 있다. Factory Interface 의 Sub Class 들이 어떻게 생성할지를 결정하는 것이 아닌, 새로운 Interface 를 두어 생성에 대한 책임을 위임한다.
또한 Abstract Factory Pattern 가 만들어내는 객체는 보통 Component (Family) 단위 정도로 보면 되며, 같은 주제를 가지는 Object 가 여러개 있고, 그 주제가 바뀔 때마다 그 Object 의 Family 가 다같이 바뀌어 껴지게 되는 패턴이다.
예를 들어, OS 별로 UI 를 만들어주는 Framework 이 있다고 해보자. 이 Framework 에서는 특정 OS 에서 구현할 UI 들의 집합을 Abstract Factory Pattern 묶어놓기 때문에 UI 를 만드려는 개발자들은 어떤 OS인지 알 필요 없이 UI 들을 생성할 수 있다.
public abstract class Button {
protected String btnText;
public Button(String btnText) {
this.btnText = btnText;
}
public void clickEvent() {
System.out.println(btnText + " 버튼을 클릭");
}
// 화면상 Component 를 그린다
// 운영체제에 따라서 Rendering 하는 동작 방식이 달라진다
public abstract void render();
}
public class WindowButton extends Button {
public WindowButton(String btnText){
super(btnText);
}
@Override
public void render() {
System.out.println("Windows Rendering 기술을 이용해 버튼을 보여줍니다");
}
}
같은 내용으로 LinuxButton 을 구현하고, 각 OS 별로 생성 메소드를 묶어놓은 Abstract Factory 를 살펴보자. 다음 인터페이스를 상속받아 OS Component 개발측은 Client 개발자들이 생성할 UI 를 해당 OS 기술을 통해 render() 될 수 있도록 매핑해 놓으면 된다.
/*
추상 Component 를 생성해주는
추상 Factory Class 이다
*/
public interface ComponentFactory {
/*
생성하려고자 하는 Component 묶음에는 생성에 필요한 필드들이 같이 들어가있다
*/
Button createButton(String btnText);
CheckBox createCheckBox(boolean isChecked);
}
/*
각 Family Group 에 맞도록 Component 들을 생성해준다
*/
public class WindowComponentFactory implements ComponentFactory {
@Override
public Button createButton(String btnText) {
return new WindowButton(btnText);
}
@Override
public CheckBox createCheckBox(boolean isChecked) {
return new WindowCheckBox(isChecked);
}
}
같은 방식으로 LinuxComponentFactory 도 구현을 한 이후, 다음과 같이 사용을 해보자.
public static void main(String [] args) {
// 이 생성된 팩토리는 따로 주입을 시켜야 함
ComponentFactory factory = new WindowComponentFactory();
// Client 측이 사용할 코드 시작 ---
// 이 팩토리를 통해서 원하는 컴포넌트를 어떤 운영체제인지 고민할 필요 없이 생성할 수 있다
Button button = factory.createButton("버튼입니다");
CheckBox checkBox = factory.createCheckBox(true);
button.render();
checkBox.render();
}
실제 사용자들이 사용할 코드는 위와 같은데, LinuComponentFactory 를 사용하는지 WindowComponentFactory 를 사용하여 자신이 원하는 Component 들을 만드는지 알 수 없다. 즉, OS 가 뭘 쓰는지 Client 의 관심사가 아니게 만든 것이다.
이처럼 Abstract Factory Pattern 은 특정 집단을 한번에 생성해줄 때 많이 사용하며, Inheritance 가 아닌 Delegation 을 사용하여 각 생성하려는 분야의 Factory 구현체에게 그 분야에서 사용되는 집단 객체들의 생성을 위임하게 된다.
Abstract Factory Pattern 은 Delegation 방식을 이용하여 Factory 를 사용하는 Client 로 부터 실제 어떤 Concrete 객체들이 생성되는지 보호하여 유지/보수에 이점을 가져간다. 또한 해당 분야를 위한 Family 객체를 Factory 구현체만 바꿈으로써 간편하게 생성/변경 할 수 있다. 하지만, Factory Interface 에 새로운 함수가 추가된다면, 모든 구현체들과 제공하는 Component 객체까지 다 생성해줘야 하기 때문에 적지 않은 비용이 든다 (하지만 이건 그냥 모든 인터페이스 단점..?).
반대로 인터페이스 변경이 아닌 Concrete 구현체들이 추가되는건 새로운 Factory Impl 과, 그 Factory 가 만들어내는 새로운 Concrete Class 들만 추가해주면 된다.
정리하며
팩토리 패턴과 비슷한걸 현업을 하면서 써본 적은 아직 없는 것 같다. 뭔가 시스템 레벨에서 사용하거나, 반복적으로 특정 객체를 요청에 맞춰 생성해내야 하는 (Abstract Object 를) 요구가 있는 서비스에선 사용하면 매우 좋을 것 같긴 하다.
둘이 조금 헷갈리긴 하는데, Factory Method Pattern 같은 경우는 '사용처'와 'abstract 한 생성 함수 노출' 을 합치는 Factory 가 있다는 것만 기억하면 되겠다. 이해가 안되는 부분은 천천이 읽어보시면 이해가 더 될 수도!
출처
1) 에릭 프리먼, 『Head First Design Patterns』, O'Reilly Media (2004)
2) 중앙대학교 소프트웨어학부 이찬근 교수님 디자인 패턴 수업자료 중
3) 원서 코드가 너무 이해하기 복잡해서 Abstract Factory Pattern 강의 코드 참고 (https://www.youtube.com/watch?v=pmKHiAIwhag)
'SW 설계 > Design Pattern' 카테고리의 다른 글
[Design Pattern] Creational - 싱글톤 패턴 (Singleton Pattern) (1) | 2023.10.21 |
---|---|
[Design Pattern] Creational - 빌더 패턴 (Builder Pattern) (0) | 2023.10.21 |
[Design Pattern] Behavioral - 중재자 패턴 (Mediator Pattern) (1) | 2023.10.20 |
[Design Pattern] Behavior - 템플릿 메소드 패턴 (Template Method Pattern) (0) | 2023.10.19 |
[Design Pattern] Behavioral - 옵저버 패턴 (Observer Pattern) (0) | 2023.10.19 |