[추상 팩토리 메서드 패턴]
다양한 구성 요소 별로 '객체의 집합'을 생성해야 할 때 유용하다. 이 패턴을 사용하여 상황에 알맞은 객체를 생성할 수 있다.
지난번에 본 생성패턴 이 가지고 있는 기본적인 형태이다. 기억하고 넘어가자.
추상팩토리는 말 그대로 객체의 집합을 생성할 때 사용한다.
이번 예제는 파스타 를 들어보기로 하자. 지난번 배운 팩토리 메서드 패턴과 함께 조합해서 사용해 보자.
파스타의 핵심 이 되는 면부터 살펴보자.
면을 위한 팩토리 메서드를 적용하는 다이어그램을 먼저 확인해 본다면
누들팩토리 와 그 구현체로 생산자와 실제 제품 들과의 관계를 분리시켰다.
noodle의 중간 abstract를 unimport 구조체가 되어 외부 패키지에서는 접근이 불가능하지만 NoodleFactory를 제공하는 제공자가 있다면 접근가능하도록 설계하였다.
package noodle
type NoodleFacotry interface {
CreateNoodle(name string) Noodle
}
type noodleFactory struct{}
func (k *noodleFactory) CreateNoodle(name string) Noodle {
switch name {
case "Fusilli":
return &Fusilli{
noodle{name: "Korean Fusilli"},
}
case "Penne":
return &Penne{
noodle{name: "Korean Penne"},
}
case "Linguine":
return &Linguine{
noodle{name: "Korean Linguine"},
}
default:
return &Spaghetti{
noodle{name: "Korean Spaghetti"},
}
}
}
func NewNoodleFactory() NoodleFacotry {
return &noodleFactory{}
}
누들 패키지에서는 NewNoodleFacotry를 통해 하나의 팩토리만 던져주게 된다. 현재는 하나의 값 만 반환하게 되지만 switch-case를 적용해 특정 팩토리 생산의 명령을 주입받는 방법도 존재한다.
package noodle
import (
"blogEx/abstoractFactory"
"fmt"
)
type Noodle interface {
abstoractFactory.Vegi
Boiling()
}
type noodle struct {
name string
}
func (n *noodle) Boiling() {
fmt.Println("면 을 삶는 중입니다..")
}
func (n *noodle) String() string {
return fmt.Sprintf("파스타 면 은 %s", n.name)
}
동일 패키지에 Noodle 인터페이스에 대한 정의와 구현체를 작성해 주었다. 구현체 내부에는 추가적인 인터페이스를 인자로 받아서
이 구현체를 상속하는 하위 구조체에서 구현을 강제하도록 만들었다.
package noodle
type Fusilli struct {
noodle
}
func (f *Fusilli) IsVegi() bool {
return false
}
type Linguine struct {
noodle
}
func (f *Linguine) IsVegi() bool {
return false
}
type Spaghetti struct {
noodle
}
func (f *Spaghetti) IsVegi() bool {
return false
}
type Penne struct {
noodle
}
func (f *Penne) IsVegi() bool {
return true
}
동일 패키지 의 하위 제품의 구현을 작성했다. 기본적인 상속과, 더불어 IsVegi()의 인터페이스를 각각 구현해 주었다.
팩토리 메서드 지난 글(https://guiwoo.tistory.com/category/Go%20Dive/Design%20Pattern)
우리 는 파스타를 만드는 데 있어 이런 재료만 필요한 것 이 아닌 추가적인 재료가 필요하다. 마저 확인해 보자. 위와 동일한 구조를 가진 소스이다.
이렇게 동일한 구성을 가지고 Vegitables와 topping 도 선언해 주자.
이렇게 생성된 팩토리 들을 각각 의 방식으로 사용할 수도 있지만 보다 클라이언트의 특정 행동에 범위를 제한하기 위해 우리는 집합체를 생성하게 된다면? 클라이언트는 이 집합체를 얻음으로써, 각각의 팩토리를 가져와 객체의 구성을 만들수 있다는 장점이 있다. 아래 그림을 보자.
각각의 팩토리 를 조합하는 파스타 키트를 운용하는 것이다. 각각의 팩토리 인터페이스를 반환해 주는 방식이다 이렇게 했을 때 장점이 무엇인가?
파스타 키트는 다양한 팩토리를 주입받을 수 있으며 심지어 각각의 팩토리에서 또한 다양한 방식으로 팩토리 제공이 가능하다.
누들 팩토리에서 이탈리안 누들을 줄수도 있고, 한국 누들 을 줄수도 있는 등 팩토리를 갈아 끼울 수 있다는 장점 이 존재한다.
코드를 확인해 보자.
type PastaKit interface {
Noodles() noodle.NoodleFacotry
Sources() source.SourceFactory
Vegetables() vegitable.VegetableFactory
Toppings() topping.ToppingFactory
}
type GuiwooPastKit struct{}
func (p *GuiwooPastKit) Noodles() noodle.NoodleFacotry {
return noodle.NewNoodleFactory()
}
func (p *GuiwooPastKit) Sources() source.SourceFactory {
return source.NewSourceFactory()
}
func (p *GuiwooPastKit) Vegetables() vegitable.VegetableFactory {
return vegitable.NewVegetableFactory()
}
func (p *GuiwooPastKit) Toppings() topping.ToppingFactory {
return topping.NewToppingFactory()
}
func NewGuiwooPastaKit() PastaKit {
return &GuiwooPastKit{}
}
파스타 키트에서는 추가적인 팩토리 메서드 적용이 아닌 생성패턴 의 기본 골격만 적용하였다. 인터페이스 와 그것에 대한 구현체
이렇게 선언된 파스타 키트를 이용해 클라이언트는 어떻게 사용할까?
func main() {
var kit pastakit.PastaKit = pastakit.NewGuiwooPastaKit()
noodle := kit.Noodles().CreateNoodle("Fusilli")
source := kit.Sources().CreateSource("Cream")
topping := kit.Toppings().CreateTopping("Chicken")
vegetables := kit.Vegetables().CreateVegetable("Paprika")
fmt.Println("pasta 끝")
noodle.Boiling()
fmt.Println(noodle)
source.Prepare()
fmt.Println(source)
topping.PutOn()
fmt.Println(topping)
vegetables.Chop()
fmt.Println(vegetables)
}
파스타 키트를 받아서 정말 다양한 파스타 의 종류 를 만들어 낼수있다.
만약 원산지가 바뀌어 버렸다면? 그냥 팩토리 내부 구현체 만 추가적으로 작성해서 넣어주면 원산지가 바뀐 파스타 키트 를 클라이언트에서는 지속적으로 사용이 가능하다.
다시 말해 클라이언트는 파스타 키트 의 원산지 재료가 무엇이든 지 간에 궁금하지 않다. 제공되는 파스타 키트에서는 누들 소스 토핑 그리고 야채들이 제공되어야 하기 때문에 정말 코드 를 호출 하는 호출 부와 제공 하는 프로덕트 부 의 느슨한 결합이 완성된 형태라고 볼수 있다.
이렇게 느슨한 결합이 주는 장점으로는 테스트 코드의 작성 즉 파스타 키트 에서 생산되는 각각의 팩토리에 대해 테스트하기 쉬우며( 목 객체를 만들기 매우 편하다.), 변화되는 코드에 있어 손쉽게 교체 및 추가할 수 있다는 점이 있다.
그러면 장점만 있는가? 위의 단순한 코드 제공에만 무려 14개의 파일과 5개의 디렉터리로 나누어져 있다. 다시 말해 복잡하다. 이해하기 어려울 수 있으며 코드의 양이 정말 많아진다는 단점이 존재한다.
그래서 그러면 팩토리 메서드 패턴 이랑 추상 팩토리랑 도대체 무엇이 다른가 에 의한 의문점이 남을수 있다.
팩토리 메소드 패턴 은? 상속을 통한 객체 생성과 오직 하나의 제품을 생산하는 쪽에 키워드가 맞춰져 있고,
추상 팩토리는? 객체의 구성을 통해 필요 객체 생성을 만들어 낼 때 즉 제품의 구성 쪽에 키워드가 맞춰져 있다.
그렇다면 이런 다른 키워드를 포워딩하고 있는데 언제 사용해야 하는가?
제품군을 만들어야 한다면? 추상팩토리를
클라이언트 코드와 인스턴스의 생성 클래스를 분리시켜야 한다면? 팩토리 메서드 패턴을 활용하면 된다.
통합 구조
통합코드
· Noodle Package
package noodle
### 팩토리 ####
type NoodleFacotry interface {
CreateNoodle(name string) Noodle
}
type noodleFactory struct{}
func (k *noodleFactory) CreateNoodle(name string) Noodle {
switch name {
case "Fusilli":
return &Fusilli{
noodle{name: "Korean Fusilli"},
}
case "Penne":
return &Penne{
noodle{name: "Korean Penne"},
}
case "Linguine":
return &Linguine{
noodle{name: "Korean Linguine"},
}
default:
return &Spaghetti{
noodle{name: "Korean Spaghetti"},
}
}
}
func NewNoodleFactory() NoodleFacotry {
return &noodleFactory{}
}
### 하위 상속 구조체 ###
package noodle
type Fusilli struct {
noodle
}
func (f *Fusilli) IsVegi() bool {
return false
}
type Linguine struct {
noodle
}
func (f *Linguine) IsVegi() bool {
return false
}
type Spaghetti struct {
noodle
}
func (f *Spaghetti) IsVegi() bool {
return false
}
type Penne struct {
noodle
}
func (f *Penne) IsVegi() bool {
return true
}
### 누들 구현체 ###
package noodle
import (
"blogEx/abstoractFactory"
"fmt"
)
type Noodle interface {
abstoractFactory.Vegi
Boiling()
}
type noodle struct {
name string
}
func (n *noodle) Boiling() {
fmt.Println("면 을 삶는 중입니다..")
}
func (n *noodle) String() string {
return fmt.Sprintf("파스타 면 은 %s", n.name)
}
·SourcePackage
package source
type SourceFactory interface {
CreateSource(name string) Source
}
type sourceFactory struct{}
func (t *sourceFactory) CreateSource(name string) Source {
switch name {
case "Tomato":
return &Tomato{
source{name: "Lagu source"},
}
case "Cream":
return &Cream{
source{name: "Creamy milk"},
}
case "Arabiata":
return &Arabiata{
source{name: "Arabiata"},
}
default:
return &Oil{
source{name: "Olive Oil"},
}
}
}
func NewSourceFactory() SourceFactory {
return &sourceFactory{}
}
### source ###
package source
import (
"blogEx/abstoractFactory"
"fmt"
)
type Source interface {
abstoractFactory.Vegi
Prepare()
}
type source struct {
name string
}
func (s *source) Prepare() {
fmt.Println(s.name, "준비 완료")
}
func (s *source) String() string {
return fmt.Sprintf("소스 는 %s 입니다.", s.name)
}
### 하위 상속 구조체 ###
package source
type Tomato struct {
source
}
func (t *Tomato) IsVegi() bool {
return false
}
type Cream struct {
source
}
func (c *Cream) IsVegi() bool {
return false
}
type Oil struct {
source
}
func (o *Oil) IsVegi() bool {
return true
}
type Arabiata struct {
source
}
func (a *Arabiata) IsVegi() bool {
return true
}
· Topping
package topping
type ToppingFactory interface {
CreateTopping(name string) Topping
}
type toppingFactory struct{}
func (t *toppingFactory) CreateTopping(name string) Topping {
switch name {
case "Chicken":
return &Chicken{
topping{name: "chicken from South Korea"},
}
case "Beef":
return &Beef{
topping{name: "beef from Australia"},
}
case "Pork":
return &Pork{
topping{name: "pork from USA"},
}
default:
return &Scampi{
topping{name: "Scampi from Norway"},
}
}
}
func NewToppingFactory() ToppingFactory {
return &toppingFactory{}
}
### topping ###
package topping
import (
"fmt"
)
type Topping interface {
PutOn()
}
type topping struct {
name string
}
func (t *topping) PutOn() {
fmt.Println(t.name, "Putting on the pasta")
}
func (t *topping) String() string {
return fmt.Sprintf("토핑은 %s 입니다.", t.name)
}
### 하위 상속 구조체 ###
package topping
type Pork struct {
topping
}
type Beef struct {
topping
}
type Scampi struct {
topping
}
type Chicken struct {
topping
}
· Vegetable
package vegitable
type VegetableFactory interface {
CreateVegetable(name string) Vegetable
}
type vegetableFactory struct{}
func (j *vegetableFactory) CreateVegetable(name string) Vegetable {
switch name {
case "SpringOnion":
return &SpringOnion{
vegetable{name: "Japanese Onion"},
}
case "Onion":
return &Onion{
vegetable{name: "Japanese Onion"},
}
case "Paprika":
return &Paprika{
vegetable{name: "Japanese Paprika"},
}
default:
return &Garlic{
vegetable{name: "Japanese Garlic"},
}
}
}
func NewVegetableFactory() VegetableFactory {
return &vegetableFactory{}
}
### vegetable ###
package vegitable
import (
"fmt"
)
type Vegetable interface {
Chop()
Slice()
}
type vegetable struct {
name string
}
func (v *vegetable) Chop() {
fmt.Printf("%s chopped", v.name)
}
func (v *vegetable) Slice() {
fmt.Printf("%s sliced", v.name)
}
### 하위 상속 구조체 ###
package vegitable
type SpringOnion struct {
vegetable
}
type Onion struct {
vegetable
}
type Paprika struct {
vegetable
}
type Garlic struct {
vegetable
}
· Pastakit
package pastakit
import (
"blogEx/abstoractFactory/noodle"
"blogEx/abstoractFactory/source"
"blogEx/abstoractFactory/topping"
"blogEx/abstoractFactory/vegitable"
)
type PastaKit interface {
Noodles() noodle.NoodleFacotry
Sources() source.SourceFactory
Vegetables() vegitable.VegetableFactory
Toppings() topping.ToppingFactory
}
type GuiwooPastKit struct{}
func (p *GuiwooPastKit) Noodles() noodle.NoodleFacotry {
return noodle.NewNoodleFactory()
}
func (p *GuiwooPastKit) Sources() source.SourceFactory {
return source.NewSourceFactory()
}
func (p *GuiwooPastKit) Vegetables() vegitable.VegetableFactory {
return vegitable.NewVegetableFactory()
}
func (p *GuiwooPastKit) Toppings() topping.ToppingFactory {
return topping.NewToppingFactory()
}
func NewGuiwooPastaKit() PastaKit {
return &GuiwooPastKit{}
}
· ClientSide
package main
import (
"blogEx/abstoractFactory/pastakit"
"fmt"
)
func main() {
var kit pastakit.PastaKit = pastakit.NewGuiwooPastaKit()
noodle := kit.Noodles().CreateNoodle("Fusilli")
source := kit.Sources().CreateSource("Cream")
topping := kit.Toppings().CreateTopping("Chicken")
vegetables := kit.Vegetables().CreateVegetable("Paprika")
fmt.Println("pasta 끝")
noodle.Boiling()
fmt.Println(noodle)
source.Prepare()
fmt.Println(source)
topping.PutOn()
fmt.Println(topping)
vegetables.Chop()
fmt.Println(vegetables)
}
출처:
https://en.wikipedia.org/wiki/Abstract_factory_pattern
https://refactoring.guru/ko/design-patterns/abstract-factory
https://sourcemaking.com/design_patterns/abstract_factory
https://www.baeldung.com/java-abstract-factory-pattern
https://www.digitalocean.com/community/tutorials/abstract-factory-design-pattern-in-java
'Go > Design Pattern' 카테고리의 다른 글
[Design Pattern] 행동패턴-전략패턴 (Strategy Pattern) (0) | 2023.07.02 |
---|---|
[Design Pattern] 생성패턴 (Singleton Pattern) (0) | 2023.05.10 |
[Design Pattern] 생성패턴 (Prototype Pattern) (0) | 2023.05.08 |
[Design Pattern] 생성패턴 (Builder Pattern) (0) | 2023.04.10 |
[Design Pattern] 생성패턴 (Factory Method) (2) | 2023.02.26 |