3. 팩토리 메소드, 추상 팩토리 패턴
디자인 패턴은 무조건 아래 두 글을 선행해야합니다. 짧으니 간단히 읽고 오시면 이해가 쉽습니다.
설명에 사용할 코드는 Java-like Pseudo Code 입니다.
여러 상태에 따른 코드
개발을 하다보면 어떤 상태에 따라 다른 플로를 작성해야할 상황이 발생합니다. 단순히 예/아니오 같은 단일 상태라면 if 문을 사용하도록 배웠고, 다중 상태라면 if-else 혹은 switch 를 사용하도록 배웠습니다. 코드는 간단하게는 로직의 나열이라고 볼 수 있는데요. 우리의 실생활에서도 이처럼 다중 상태에 따라 다양한 작업을 수행하곤 합니다. 결국 모든 실생활도 if-else/switch 로 설명이 가능하다는 의미겠지요.
if-else 에 “의존한” 처리
- 라면 종류별 끓이기라면 종류를 상태로 본다면 명시해준 라면 종류 String type 따라서 다른 라면을 끓입니다. 함수 makeRamen(String type) 은 아래와 같이 두 파트로 나눠볼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13class 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);
}
}
- 상태:
1.1. 라면 종류를 고르고 - 처리: 라면을 끓입니다.
상태와 처리라는 두 책임이 하나의 코드에 모여있군요. 1.상태와 2.처리를 한번 떼어내볼까요.
if-else 를 “분리한” 처리
- 라면 끓이기
1
2
3
4
5
6
7class RamemMaker {
public void makeRamen(String type) {
Water water = new Water(100);
Ramen ramen = ramenGetter.getRamen(type);
heat(water);
water.add(ramen);
} - 라면 종류별 생성라면 종류별 생성을 책임지는 상태 함수는 재사용성을 갖게되었고, 상태 책임이 더 명확해 졌습니다. 여기서 함수 getRamen(String type)와 같이 상태에 따라 알맞은 클래스를 만들어서 주입해주는 개념을 팩토리라고 합니다.
1
2
3
4
5
6
7
8public Ramen getRamen(String type) {
if (type == "볶음") {
return 볶음라면;
} else if (type == "국물") {
return 국물라면;
}
}
}
팩토리는 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
9abstract 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
6class FriedRamenMaker extends RamenMaker {
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
13if (type == "볶음") {
if (ingredient == "해물") {
return 해물_볶음라면;
} else if (ingredient == "고기") {
return 고기_볶음라면;
}
} else if (type == "국물") {
if (ingredient == "해물") {
return 해물_국물라면;
} else if (ingredient == "고기") {
return 고기_국물라면;
}
}
상태:
1.1. 라면 종류를 고르고
1.2. 재료를 고르고처리: 라면을 끓입니다.
이번에는 1.상태와 2.처리라는 두 책임뿐 아니라 **두 상태인 1.1.과 1.2.**도 나누어야겠군요.
- 라면 끓이기
1
2
3
4
5
6
7class 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
9IngredientFactory ingredientFactory = new MeatIngredientFactory();
public Ramen getRamen(String type) {
if (type == "볶음") {
return new FriedRamen(ingredientFactory));
} else if (type == "국물") {
return new StewRamen(ingredientFactory));
}
}
} - 라면에 들어갈 재료
1
2
3
4interface IngredientFactory {
public Broth getBroth();
public Flakes getFlakes();
} - 라면에 들어갈 고기 재료라면 종류가 함수 내 if-else 로 분기를 탔다면, 라면에 들어갈 재료는 추상 팩토리를 통해 어떤 재료든지 넣을 수 있도록 하였습니다. 전자를 팩토리 후자를 추상 팩토리라고 합니다. 1) 어떤 종류의 라면인지는 팩토리(RamenGetter.getRamen)**에서 선택하고, **추상 팩토리(IngredientFactory)**의 구상 팩토리를 통해 **2) 특정 재료를 넣어주면 최종 라면 결과물이 나옵니다.
1
2
3
4
5
6
7
8
9
10class MeatIngredientFactory implements IngredientFactory {
// 고기 육수
public Broth getBroth() {
return new MeatBroth();
}
// 고기 건더기
public Flakes getFlakes() {
return new MeatFlakes();
}
}
추상 팩토리 패턴
처음 팩토리를 배울때 “팩토리 메서드 패턴”과 “추상 팩토리 패턴” 두 패턴의 차이를 이해하는데 꽤나 힘들었습니다. 하지만 **팩토리(Factory)-결과물(Product)**의 개념을 잘 이해한다면 어렵지 않습니다.
1) 팩토리 메서드 패턴은 추상 팩토리 메서드를 각 종류에 따라 구현해서 결과물(Product)을 바로 반환(Return)**했다면,
**2) 추상 팩토리 패턴은 추상 팩토리(Interface 혹은 Abstract)에 따라 결과물(Product)을 다르게 **생성(Make & Return)**한다.
- 1) 팩토리 메서드 패턴: 볶음/국물 결정해서 바로 반환
1
2
3
4
5
6class FriedRamenMaker extends RamenMaker {
...
public Ramen getRamen(String type) {
return new FriedRamen();
}
} - 2) 추상 팩토리 패턴: 볶음/국물 생성을 위한 재료 추상 클래스를 정의
1
2
3
4
5
6
7
8
9
10
11class 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)**한다.
3. 팩토리 메소드, 추상 팩토리 패턴
https://aaronryu.github.io/2019/02/22/factory-method-and-abstract-factory-pattern/