Factory Method
개요
팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴이다.
이는 클라이언트테서 직접 new
연산자를 통해 제품 객체를 생성하는 것이 아닌, 제품 객체들을 생성하는 공장 클래스를 만들고,
이를 상속하는 서브 공장 클래스의 메서드에서 여러가지 제품 객체 생성을 각각 책임 지는 것으로 객체 생성에 필요한 과정을 템플릿 처럼 미리 구성해놓고, 객체 생성에 관한 전처리나 후처리를 통해 생성 과정을 다양하게 처리하여 객체를 유연하게 정할 수 있는 특징이 있다.
적용 시기
팩토리 메서드는 함께 동작해야 하는 객체들의 정확한 유형과 의존 관계를 모르는 경우
팩토리 메서드는 객체 생성 코드와 실제로 사용하는 코드를 분리해 독립적으로 사용하고 이를 통해 확장성이 좋아진다.
예를 들어, 앱에 새로운 제품을 추가하려면 새로운 자식 클래스를 생성한 후 해당 클래스 내부에 팩토리 메서드를 오버라이딩 하기만 하면
된다.
라이브러리 혹은 프레임워크 사용자들에게 내부 컴포넌트를 확장하는 방법을 제공할 경우
상속은 라이브러리나 프레임워크의 디폴트 행동을 확장하는 가장 쉬운 방법 중 하나이지만 프레임워크는 표준 컴포넌트 대신 직접 정의한 자식 클래스를 사용해야 한다는 것을 어떻게 인식할 수 있을까?
해결책은 일단 프레임워크 전체에서 컴포넌트들을 생성하는 코드를 단일 팩토리 메서드로 줄인 후, 누구나 이 팩토리 메서드를 오버라이드 할 수 있도록 하는 것이다.
기존 객체들을 매번 재구축하는 대신 재사용을 통해 시스템 리소스를 절약하고싶을 경우
이러한 요구 사항은 데이터베이스 연결, 파일 시스템 및 네트워크처럼 시스템 자원을 많이 사용하는 대규모 객체들을 처리할 때 자주 발생한다.
그렇다면 기존 객체를 재사용하려면 무엇을 해야할까?
- 먼저 생성된 모든 객체를 추적하기 위해 스토리지를 생성해야 한다.
- 누군가가 객체를 요청하면 프로그램은 해당 풀 내에서 유휴 객체를 찾는다.
- 이 객체를 클라이언트 코드에 반환한다.
- 유휴 객체가 없다면 프로그램은 새로운 객체를 생성하고 풀에 해당 객체를 추가한다.
이 조건을 만족하면서 코드를 배치할 수 있는 가장 확실하고 편리한 위치는 우리가 재사용하려는 객체의 클래스 생성자일 것이다.
하지만 생성자 특성상 항상 새로운 객체들을 반환하며, 기존 인스턴스를 반환할 수 없기 때문에 새 객체들을 생성하고
기존 객체를 재사용할 수 있는 일반적인 메서드 즉, 팩토리 메서드가 필요하다.
패턴 장점
- 생성자와 구현 객체의 강한 결합을 피할 수 있다.
- 단일 책임 원칙 준수
제품 생성 코드를 프로그램의 한 위치(패키지, 클래스 등)로 이동하여 코드를 더 쉽게 유지 관리할 수 있다. - 개방 폐쇄 원칙 준수
기존 코드를 수정하지 않고 새로운 유형의 제품 인스턴스를 프로그램에 도입할 수 있어 원칙을 만족한다.(확장성 있는 전체 프로젝트 구성이 가능) - 생성에 대한 인터페이스 부분과 구현 부분이 나뉘었기 때문에 패키지 분리를 통한 협업이 가능하다.
패턴 단점
- 패턴을 구현하기 위해 각 구현체마다 팩토리 객체들을 모두 구현해야한다. 구현체가 늘어날수록 팩토리 클래스가 증가하여 서브 클래스 수가 늘어난다.
- 코드 복잡성이 증가한다.
이를 해결하기 위한 가장 좋은 방법은 생성 클래스들의 기존 계층 구조에 패턴을 도입하는 것이다.
팩토리 메서드 패턴 구조
(1) Creator
주의
크리에이터라는 이름을 가지고 있지만 크리에이터의 주책임은 제품을 생성하는 것이 아니다.
일반적으로 크리에이터 클래스에는 제품과 관련된 핵심 비지니스 로직이 있으며, 팩토리 메서드는 이 로직을 구현한 제품 클래스들로부터 디커플링 하는데 도움을 줄 뿐이다.
회사에 프로그래머들을 교육하는 부서가 있을 수 있지만 회사의 주 임무는 프로그래머를 교육하는 것이 아닌 코드를 작성하는 것이다.
크리에이터 클래스는 새로운 제품 객체들을 반환하는 펙토리 메서드를 선언한다. 중요한 점은 이 팩토리 메서드의 반환 유형이 제품 인터페이스와 일치해야 한다는 것이다.
팩토리 메서드를 추상(abstract)로 선언하여 모든 자식 클래스들이 각각 이 메서드의 자체 버전을 구현하도록 강제 할 수 있고, 기초 팩토리 메서드가 디폴드 값을 갖는 제품 유형을 반환하도록 만들 수 있다.
- 객체 생성 처리 메서드(someOperation) :
객체 생성에 관한 전처리, 후처리를 템플릿화한 메서드 - 팩토리 메서드(createProduct) :
서브 공장 클래스에서 재정의할 객체 생성 추상 메서드
(2) Concrete Creator
각 서브 공장 클래스들은 이에 맞는 제품 객체를 반환하도록 생성 추상 메서드를 재정의한다.
즉, 제품 객체 하나당 그에 걸맞는 생산 공장 객체가 위치된다.
추가로 팩토리 메서드는 항상 새로운 인스턴스를 생성할 필요가 없다. 기존 객체들을 캐시, 객체 풀 또는 다른 소스로부터 반환할 수 있다.
(3) Product
제품은 인터페이스로 선언한다.
인터페이스는 생성자와 자식 클래스들이 생성할 수 있는 모든 객체에 공통이다. (제품 구현체를 추상화)
(4) Concrete Product
구현 제품들은 제품 인터페이스의 다양한 구현체이다.
정리
팩토리 메서드 패턴은 객체를 만들어내는 공장(Factory 객체)를 만드는 패턴이고 어떤 클래스의 인스턴스를 생성할 것인지는 미리 정의된 팩토리 서브 클래스에서 결정한다.
Factory Method 패턴 구현
Product.java
Factory에서 생성되는 객체의 기본이 되는 클래스
package com.design.pattern.creational.factorymethod.framwork;
public abstract class Product {
public abstract void use();
}
Factory.java
Factory의 기본 클래스로 인스턴스를 생성
package com.design.pattern.creational.factorymethod.framwork;
public abstract class Factory {
public final Product create(String owner) {
Product product = createProduct(owner);
registerProduct(product);
return product;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
IDCard.java
package com.design.pattern.creational.factorymethod.idcard;
import com.design.pattern.creational.factorymethod.framwork.Product;
public class IdCard extends Product {
private final String owner;
IdCard(String owner) {
System.out.println(owner + "의 카드를 생성합니다.");
this.owner = owner;
}
@Override
public void use() {
System.out.println(owner + "의 카드를 사용합니다.");
}
public String getOwner() {
return owner;
}
}
IDCardFactory.java
Factory 클래스로 정의된 메소드를 구현하는 구상 클래스
package com.design.pattern.creational.factorymethod.idcard;
import com.design.pattern.creational.factorymethod.framwork.Factory;
import com.design.pattern.creational.factorymethod.framwork.Product;
import java.util.ArrayList;
public class IDCardFactory extends Factory {
private final ArrayList<String> owners = new ArrayList<>();
@Override
protected Product createProduct(final String owner) {
return new IdCard(owner);
}
@Override
protected void registerProduct(final Product product) {
IdCard idCard = (IdCard) product;
String owner = idCard.getOwner();
owners.add(owner);
}
public ArrayList<String> getOwners() {
return owners;
}
}
Main.java
사용 확인
package com.design.pattern;
import com.design.pattern.creational.factorymethod.framwork.Factory;
import com.design.pattern.creational.factorymethod.framwork.Product;
import com.design.pattern.creational.factorymethod.idcard.IDCardFactory;
public class Main {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("Object");
Product card2 = factory.create("Clean Architecture");
Product card3 = factory.create("Pragmatic");
card1.use();
card2.use();
card3.use();
}
}
실행 결과
디자인 패턴 비교
팩토리 메서드 패턴 | 추상 팩토리 패턴 | |
---|---|---|
목표 | 클래스를 구체적으로 지정하지 않고 객체를 생성한다. | 클래스를 구체적으로 지정하지 않고 관련 또는 의존적인 객체의 패밀리를 생성하는 인터페이스를 제공한다. |
유형 | 생성 패턴 | 생성 패턴 |
구성 | 1. Creator: 팩토리 메서드를 선언한다. 2. ConcreteCreator: 객체를 생성하기 위한 팩토리 메서드를 구현한다. 3. Product: 팩토리 메서드에 의해 생성된 객체의 인터페이스를 정의한다. 4. ConcreteProduct: Product 인터페이스를 구현한다. |
1. AbstractFactory: 제품 객체를 생성하기 위한 인터페이스를 선언한다. 2. ConcreteFactory: AbstractFactory를 구현하여 특정 제품 패밀리를 생성한다. 3. AbstractProduct: 제품 객체의 인터페이스를 선언한다. 4. ConcreteProduct: AbstractProduct 인터페이스를 구현한다. |
제품 수 | 일반적으로 하나의 제품 유형(예: 객체 패밀리)을 생성한다. | 관련이 있거나 의존적인 여러 제품(예: 다양한 객체 패밀리)을 생성한다. |
용도 | 하나의 제품 또는 밀접하게 관련된 제품을 생성해야 할 경우 유용하다. | 여러 패밀리의 제품이 다양하게 변할 때 및 구체적인 클래스를 지정하지 않고 패밀리 전체의 제품을 생성해야 할 때 유용하다. |
관계 | 하위 클래스가 팩토리 메서드의 구체적인 구현을 제공하기 위해 상속을 사용한다. | 구성을 사용하며 관련된 팩토리(추상 팩토리) 집합을 정의하고 각각이 여러 관련 제품(ConcreteProduct)을 생성한다. |
복잡성 | 추상 팩토리 패턴보다 간단하고 직관적이다. | 팩토리 패턴보다 복잡하며 여러 팩토리 인터페이스와 그 구현을 정의해야 한다. |
가변성 | 하위 클래스가 생성할 구체적인 제품을 선택함으로써 가변성을 제공한다. | 여러 구체적인 팩토리가 다양한 제품 패밀리를 생성하므로 더 높은 가변성을 제공한다. |
적용 가능성 | 하나의 제품 또는 밀접하게 관련된 제품을 생성해야 하는 상황에 적합 | 여러 관련 또는 의존적인 제품 패밀리가 있고 구체적인 클래스를 지정하지 않고 패밀리 전체의 제품을 생성해야 하는 상황에 적합 |
팩토리 메서드 패턴 | 템플릿 메서드 패턴 | |
---|---|---|
공통점 | 추상 메서드를 정의하고 서브 클래스가 추상 메서드를 구현 한다. | 템플릿화된 메서드에서 로직을 공통화 하기도 한다. |
차이점 | 구현할 대상은 객체 생성 알고리즘을 지정 | 구현할 대상은 실행할 전략 알고리즘을 지정 |
정리
- 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌터 패턴으로 발전해 나간다.
- 추상 팩토리 클래스들은 팩토리 매서드들의 집합을 기반으로하는 경우가 많다. 하지만 프로토타입을 사용하여 추상 팩토리의 구상 클래스들의 생성 메서드들을 구현할 수도 있다.
- 팩토리 메서드를 반복자와 함께 사용하여 컬렉션 자식 클래스들이 해당 컬렉션들과 호환되는 다양한 유형의 반복자들을 반환하도록 할 수 있다.
- 프로토타입은 상속을 기반으로 하지 않으므로 상속과 관련된 단점들이 없다. 반면 프로토타입은 복제된 객체의 복잡한 초기화 과정이 필요하고, 팩토리 메서드는 상속을 기반으로 하고 초기화 단계가 필요하지 않다.
- 팩토리 메서드는 템플릿 메서드의 특수화라고 생각할 수 있고 동시에 대규모 템플릿 메서드의 한 단계의 역할을 팩토리 메서드가 할 수 있다.
참고
Design Pattern | Factory Method (팩토리 메소드)
Factory Method 패턴이란? Factory라는 영어 단어는 공장 이라는 의미가 된다. 공장에서는 물건을 스펙에 맞게 생산하고 요구사항이 좀 달라지면 그에 맞게 조금 다른 물건도 생산을 하게 된다. 원하는
www.devkuma.com