3. 팩토리 메소드, 추상 팩토리 패턴

디자인 패턴은 무조건 아래 두 글을 선행해야합니다. 짧으니 간단히 읽고 오시면 이해가 쉽습니다.

설명에 사용할 코드는 Java-like Pseudo Code 입니다.


여러 상태에 따른 코드

개발을 하다보면 어떤 상태에 따라 다른 플로를 작성해야할 상황이 발생합니다. 단순히 예/아니오 같은 단일 상태라면 if 문을 사용하도록 배웠고, 다중 상태라면 if-else 혹은 switch 를 사용하도록 배웠습니다. 코드는 간단하게는 로직의 나열이라고 볼 수 있는데요. 우리의 실생활에서도 이처럼 다중 상태에 따라 다양한 작업을 수행하곤 합니다. 결국 모든 실생활도 if-else/switch 로 설명이 가능하다는 의미겠지요.

if-else 에 “의존한” 처리

  • 라면 종류별 끓이기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class RamemMaker {
    public void makeRamen(String type) {
    Water water = new Water(100);
    Ramen ramen;
    heat(water);
    if (type == "볶음") {
    ramen = 볶음라면;
    } else if (type == "국물") {
    ramen = 국물라면;
    }
    water.add(ramen);
    }
    }
    라면 종류를 상태로 본다면 명시해준 라면 종류 String type 따라서 다른 라면을 끓입니다. 함수 makeRamen(String type) 은 아래와 같이 두 파트로 나눠볼 수 있습니다.
  1. 상태:
    1.1. 라면 종류를 고르고
  2. 처리: 라면을 끓입니다.

상태와 처리라는 두 책임이 하나의 코드에 모여있군요. 1.상태와 2.처리를 한번 떼어내볼까요.

if-else 를 “분리한” 처리

  • 라면 끓이기
    1
    2
    3
    4
    5
    6
    7
    class RamemMaker {
    public void makeRamen(String type) {
    Water water = new Water(100);
    Ramen ramen = ramenGetter.getRamen(type);
    heat(water);
    water.add(ramen);
    }
  • 라면 종류별 생성
    1
    2
    3
    4
    5
    6
    7
    8
        public Ramen getRamen(String type) {
    if (type == "볶음") {
    return 볶음라면;
    } else if (type == "국물") {
    return 국물라면;
    }
    }
    }
    라면 종류별 생성을 책임지는 상태 함수재사용성을 갖게되었고, 상태 책임이 더 명확해 졌습니다. 여기서 함수 getRamen(String type)와 같이 상태에 따라 알맞은 클래스를 만들어서 주입해주는 개념팩토리라고 합니다.

팩토리는 if-else/switch 와 같이 상태에 따라 다른 클래스를 생성 및 주입해주는 개념를 의미합니다.
즉 **어떤 상태(What)**인지에 따라 **어떻게 처리(How)**할지가 다릅니다.

팩토리 메서드 패턴

상태에 따라서 처리를 하기위해 RamemMaker.getRamen 함수 내부에서 if-else 문을 이용하여 분기를 탑니다. 이를 RamemMaker 의 추상 메서드로 만든다면 볶음라면(FriedRamemMaker), 국물라면(StewRamenMaker)에 따라 각각에서 getRamen 를 알맞게 구현하면 됩니다. 기존 if-else 기반 getRamen 을 간단히 팩토리라고 한다면 구현에 따라 달라지는 getRamen 추상 함수를 팩토리 메서드라고 합니다.

  • 라면 생성 + 라면 끓이기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    abstract class RamemMaker {
    protected abstract Ramem getRamen(String type);
    public void makeRamen(String type) {
    Water water = new Water(100);
    Ramen ramen = getRamen(type);
    heat(water);
    water.add(ramen);
    }
    }
  • 라면 종류별(볶음) 생성 + 라면 끓이기
    1
    2
    3
    4
    5
    6
    class FriedRamenMaker extends RamenMaker {
    @Override
    public Ramen getRamen(String type) {
    return new FriedRamen();
    }
    }

    팩토리 메서드 패턴은 팩토리 개념을 추상함수를 통해 원하는 구현 클래스를 반환하도록 하는것입니다.

if-else + if-else

팩토리는 단순히 한 상태에 따른 구현(결과물) 클래스를 생성합니다. 라면은 라면 종류라는 한 상태뿐만 아니라 재료라는 추가 상태로도 세분화될 수 있습니다. 두 개의 상태가 생겼군요. 이를 이차원 상태로 보면 아래와 같이 if-else 문 안에 또 하나의 if-else 문을 갖는 구조로 볼 수 있습니다.

  • 이차원 상태: 라면 업체 + 라면 종류
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if (type == "볶음") {
    if (ingredient == "해물") {
    return 해물_볶음라면;
    } else if (ingredient == "고기") {
    return 고기_볶음라면;
    }
    } else if (type == "국물") {
    if (ingredient == "해물") {
    return 해물_국물라면;
    } else if (ingredient == "고기") {
    return 고기_국물라면;
    }
    }
    같은 볶음라면이지만 어떤 재료를 사용했는지에 따라서도 나눌 수 있는것이죠. 일차원 상태분기는 비교적 쉬웠습니다. 이차원 상태를 고려하도록 확장하려면 아래와 같이 될텐데요.
  1. 상태:
    1.1. 라면 종류를 고르고
    1.2. 재료를 고르고

  2. 처리: 라면을 끓입니다.

이번에는 1.상태와 2.처리라는 두 책임뿐 아니라 **두 상태인 1.1.과 1.2.**도 나누어야겠군요.

  • 라면 끓이기
    1
    2
    3
    4
    5
    6
    7
    class RamemMaker {
    public void makeRamen(String type) {
    Water water = new Water(100);
    Ramen ramen = ramenGetter.getRamen(type);
    heat(water);
    water.add(ramen);
    }
  • 라면 종류별 생성
    1
    2
    3
    4
    5
    6
    7
    8
    9
        IngredientFactory ingredientFactory = new MeatIngredientFactory();
    public Ramen getRamen(String type) {
    if (type == "볶음") {
    return new FriedRamen(ingredientFactory));
    } else if (type == "국물") {
    return new StewRamen(ingredientFactory));
    }
    }
    }
  • 라면에 들어갈 재료
    1
    2
    3
    4
    interface IngredientFactory {
    public Broth getBroth();
    public Flakes getFlakes();
    }
  • 라면에 들어갈 고기 재료
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class MeatIngredientFactory implements IngredientFactory {
    // 고기 육수
    public Broth getBroth() {
    return new MeatBroth();
    }
    // 고기 건더기
    public Flakes getFlakes() {
    return new MeatFlakes();
    }
    }
    라면 종류가 함수 내 if-else 로 분기를 탔다면, 라면에 들어갈 재료는 추상 팩토리를 통해 어떤 재료든지 넣을 수 있도록 하였습니다. 전자를 팩토리 후자를 추상 팩토리라고 합니다. 1) 어떤 종류의 라면인지는 팩토리(RamenGetter.getRamen)**에서 선택하고, **추상 팩토리(IngredientFactory)**의 구상 팩토리를 통해 **2) 특정 재료를 넣어주면 최종 라면 결과물이 나옵니다.

추상 팩토리 패턴

처음 팩토리를 배울때 “팩토리 메서드 패턴”과 “추상 팩토리 패턴” 두 패턴의 차이를 이해하는데 꽤나 힘들었습니다. 하지만 **팩토리(Factory)-결과물(Product)**의 개념을 잘 이해한다면 어렵지 않습니다.

1) 팩토리 메서드 패턴은 추상 팩토리 메서드를 각 종류에 따라 구현해서 결과물(Product)을 바로 반환(Return)**했다면,
**2) 추상 팩토리 패턴
추상 팩토리(Interface 혹은 Abstract)에 따라 결과물(Product)을 다르게 **생성(Make & Return)**한다.

  • 1) 팩토리 메서드 패턴: 볶음/국물 결정해서 바로 반환
    1
    2
    3
    4
    5
    6
    class FriedRamenMaker extends RamenMaker {
    ...
    public Ramen getRamen(String type) {
    return new FriedRamen();
    }
    }
  • 2) 추상 팩토리 패턴: 볶음/국물 생성을 위한 재료 추상 클래스를 정의
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MeatRamenMaker extends RamenMaker {
    ...
    IngredientFactory ingredientFactory = new MeatIngredientFactory();
    public Ramen getRamen(String type) {
    if (type == "볶음") {
    return new FriedRamen(ingredientFactory));
    } else if (type == "국물") {
    return new StewRamen(ingredientFactory));
    }
    }
    }

팩토리

상태에 따라 그에 맞는 **결과물(Product)**를 반환합니다.

팩토리 메서드 패턴

추상 팩토리 메서드를 각 상태에 따라 구현하여 **결과물(Product)**을 바로 반환합니다.

추상 팩토리 패턴

추상 팩토리(Interface 혹은 Abstract)에 따라 결과물(Product)을 다르게 **생성(Make & Return)**한다.

Author

Aaron Ryu

Posted on

2019-02-22

Updated on

2020-08-19

Licensed under

Comments