팩토리 메서드 패턴이란?
팩토리 패턴의 한 종류이며 객체 생성 과정을 캡슐화하는 패턴이다. refactoring.guru와 위키백과에서는 각각 다음과 같이 정의한다.
- refactoring.guru
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
객체를 생성하기 위한 인터페이스를 제공하지만 만들어지는 객체의 타입(class)을 결정하는 것은 서브클래스에게 위임하는 creational design pattern이다.
- 위키백과
팩토리 메서드 패턴 (Factory method pattern)은 객체지향 디자인 패턴이다. Factory method는 부모(상위) 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴이며. 자식(하위) 클래스가 어떤 객체를 생성할지를 결정하도록 하는 패턴이기도 하다. 부모(상위) 클래스 코드에 구체 클래스 이름을 감추기 위한 방법으로도 사용한다.
⇒ 부모 클래스가 직접 구상 클래스를 생성하지 않도록 자식클래스에게 위임하며 부모 클래스에게 구체 클래스 이름을 감추는 기능 또한 수행한다.
구조

- Product 객체와 연관된 핵심 비즈니스 로직은 Creator 내에, 객체의 생성 코드 및 생성 책임은 ConcreteCreator 내에 존재한다.
- Creator의 서브클래스에 팩토리 메소드를 정의하여 팩토리 메소드 호출로 적절한 ConcreteProduct 인스턴스를 반환하게 한다. 이 때 Creator의 구현 방법에 두 가지가 존재한다.
- Creator를 추상 클래스로 정의하고 팩토리 메소드는 abstract로 선언하는 방법.
- Creator를 구체 클래스로 정의하고 팩토리 메소드의 기본 구현을 제공하는 방법.
- 팩토리 메소드가 항상 새로운 객체를 생성하는 것은 아니다. 캐시나 객체 풀, 혹은 다른 소스에서부터 이미 존재하는 객체를 가져올 수도 있다.
다른 패턴과의 관계
- 추상 메소드 패턴, 프로토타입 패턴, 빌더 패턴 등이 팩토리 메소드 패턴을 더 발전시킨 것이라고 볼 수 있다.
- Behaviour Pattern중 하나인 Template Method 패턴을 전문화 한 것으로 큰 규모의 Template Method 패턴 내에서 한 단계로 제공될 수 있다.
장점
- 객체의 생성은 Creator가 아닌 서브 클래스에 위임하고 객체의 사용 로직은 Creator에 그대로 위치하게 두는 방식으로 Creator가 Concrete Product에 종속되는 것을 막아 결합도를 낮출 수 있다.
- Creator는 객체의 사용 로직에 대한 책임만을, 그 서브클래스는 객체의 생성에 대한 책임만을 가진다. 따라서 각 클래스는 각각 한 개의 책임만을 가지게 되고, “클래스를 변경하는 이유는 단 한개여야 한다.” 는 SRP(단일 책임 원칙)을 잘 지킬 수 있게 된다.
- Product 인터페이스를 상속받는 새로운 클래스를 생성하고 싶을 때 존재하는 코드에 대한 수정 없이 해당 클래스 및 그에 맞는 Creator의 서브 클래스 코드만 추가해주면 된다. 따라서 “확장엔 열려있고 수정/변경엔 닫혀있다.”는 OCP(개방/폐쇄 원칙)을 잘 지킬 수 있게 된다.
단점
- Creator를 상속해서 사용하지만 부모 클래스를 전혀 확장하지 않는다. 따라서 이 패턴은 extends를 관계를 잘못 이용한 것으로 볼 수 있다.
- 패턴을 구현할 때 너무 많은 새로운 서브 클래스들을 추가하게 된다면 코드가 과하게 복잡해질 수 있다. 따라서 이 패턴의 가장 좋은 사용법은 이미 존재하는 Creator 클래스의 계층에 도입하는 것이다.
구현 방법 및 예제
- 같은 종류의 Product들의 기본이 되는 Interface를 생성한다.
interface Chicken { void cook(); }
- 로직에 필요한 ConcreteProduct class들을 생성한다. 이 때 해당 class 들은 앞서 선언한 Interface를 상속한다.
class BBQFriedChicken implements Chicken { @Override void cook() { System.out.println("BBQ 후라이드 튀기는 중"); } }
class BHCFriedChicken implements Chicken { @Override void cook() { System.out.println("BHC 후라이드 튀기는 중"); } }
class BHCSeasonedChicken implements Chicken { @Override void cook() { System.out.println("BHC 후라이드 튀기는 중"); System.out.println("BHC 후라이드에 양념 바르는 중"); }
- Creator class를 생성해준다. 이 때 내부에는 빈 Factory method가 필수로 필요하다. Factory Method의 return type은 product 인터페이스여야 한다.
- 이미 구현되어 있는 코드에 팩토리 메소드 패턴을 적용하는 거라면 creator 내부에서 Product의 Constructor를 사용하는 부분들을 전부 factory method 호출로 변경해준다.
ex) Chicken chicken = new BBQChicken() ⇒ Chicken chicken = createChicken()
abstract class ChickenStore { public Chicken orderChicken(String type) { //Chicken chicken = new BBQChicken(type); 이런식으로 되어 있는걸 Chicken chicken = createChicken(type); // 이렇게 치환해줌 chicken.cook(); return chicken; } abstract Chicken createChicken(String type) }
- 앞서 생성했던 ConcreteProduct들에 맞춰 ConcreteCreator들을 생성한다. 이 class들은 Creator의 서브클래스로 Creator의 factoryMethod를 Override해 재정의 해야한다. 즉, 이 클래스들이 객체 생성의 주체가 되며 그 책임을 가진다.
- 이 때 ConcreteProduct 타입이 너무 많고 모든 타입들에 대한 ConcreteCreator들을 만드는 것이 의미가 없다면 매개변수를 이용해 ConcreteCreator 클래스에서 제어할 수 있다.
ex) 아래의 BHCStore 예제
- 이 때 잘못된 매개변수가 들어왔을 때를 대비해 예외처리나 Enum을 사용하는 등 처리를 해 주는 것도 고려해 볼만 한 상황이다. (이번 예제에서는 처리X)
class BBQStore extends ChickenStore { @Override Chicken createChicken(String type) { return new BBQFriedChicken(); } } class BHCStore extends ChickenStore { @Override Chicken createChicken(String type) { if(type.equals("special") { return new BHCSeasonedChicken(); } return new BHCFriedChicken(); } }
public class Main { public static void main(String[] args) { ChickenStore bbqStore = new BBQStore(); ChickenStore bhcStore = new BHCStore(); Chicken bbqFriedChicken = bbqStore.orderChicken(""); Chicken bhcFriedChicken = bhcStore.orderChicken(""); Chicken bhcSeasonedChicken = bhcStore.orderChicken("special"); } }
Spring 에서의 팩토리 메소드 패턴
Spring에서는 하나의 인터페이스를 상속받고 있는 구현체들인 bean들을 Collection으로 주입할 수 있다. 이는 팩토리 메서드 에서 유용하게 사용될 수 있다. ConcreteCreator들을 따로 만들어 줄 필요 없이 해당 bean들을 사용하면 되기 때문이다.
interface ChickenService { void cook(); ChickenType getType(); }
public enum ChickenType { BBQFried, BHCFried,BHCSeasoned }
@Service class BBQFriedChickenService implements ChickenService { @Override void cook() { System.out.println("BBQ 후라이드 튀기는 중"); } @Override String getType() { return ChickenType.BBQFried; } }
@Service class BHCFriedChickenService implements ChickenService { @Override void cook() { System.out.println("BHC 후라이드 튀기는 중"); } @Override String getType() { return ChickenType.BHCFried; } }
@Service class BHCSeasonedChickenService implements ChickenService { @Override void cook() { System.out.println("BHC 후라이드 튀기는 중"); System.out.println("BHC 후라이드에 양념 바르는 중"); } @Override String getType() { return ChickenType.BHCSeasoned; } }
@Component public class ChickenServiceStore { private Map<ChickenType, ChickenService> chickenServiceMap = new HashMap<>(); public ChickenServiceStore(List<ChickenService> chickenServices) { if(CollectionUtils.isEmpty(chickenServices)) { throw new IllegalArgumentException("존재하지 않는 Chicken입니다."); } for (ChickenService chickenService : chickenServices) { this.chickenServiceMap.put(chickenService.getType(), chickenService); } } //얘가 팩토리 메서드 public ChickenService createService(ChickenType type) { return chickenServiceMap.get(type); } }
@Service public class ChickenOrderService { private final ChickenServiceStore chickenServiceStore; public ChickenOrderService( ChickenServiceStore chickenServiceStore ) { this.chickenServiceStore = chickenServiceStore; } public void receiveOrder(OrderContent orderContent) { chickenServiceStore.createService(orderContent.getFoodType()).cook(); } }