추상 팩토리 디자인 패턴
추상 팩토리 디자인을 사용해 보겠습니다.
아마 여러분이 디자인 패턴을 한 번이라도 봤다면 이 패턴을 가장 처음 보게 될 텐데
이게 디자인 패턴 중 가장 친근할 겁니다.
요약하자면
일정한 패턴을 가진 객체들을 자주 사용할 때
공통된 함수나 변수로 묶어서 인터페이스를 만들고, 이 인터페이스를 잘 활용을 하는 방법입니다.
이걸 구현하는 하위 객체들을 사용하는, 호출 부에서는 객체 하나하나의 생성을 로직을 몰라도 바로 쓸 수 있습니다.
이로써 객체 생성의 추상화가 구현됨으로써 유연성과 확장성을 높여 줍니다.
개념
추상화라는 말이 들어가는 걸로, 알 수 있듯이 객체 지향 언어에서 인터페이스를 사용해서 주로 구현합니다.
- 여러 객체를 생성하기 위해 이들을 묶는 상위 개념의 인터페이스 제공
- 각 객체는 Factory (생성 함수)를 통해 생성됨
- 클라이언트는 Factory(생성 함수)의 인터페이스만 알면 됨
이로 인해 아래 장점을 얻습니다.
- 객체 생성을 팩토리 함수에서 함으로써 클라이언트는 생성 방법을 몰라도 됨
이게 가장 큰 장점으로 일일이 로직을 분석하지 않아도 돼서 유지보수에서 용이함 - 동일한 팩토리 함수를 쓰므로 객체 간의 호환성을 유지
- 이미 있는 패턴을 따라 하면 되므로 추후에 기능을 추가하기도 유리
즉 아래 이미지로 요약이 가능합니다
사용 예시로는 무엇을 할까 고민했는데..
제가 지금 다니는 회사는 안과 데이터를 다루는데.
이때 한 환자에 대해 여러 진단이 들어가게 됩니다.
그때 아주 용이하게 쓸 수 있을 것 같아서 이걸로 예시를 들었습니다.
진단마다 고정된 정보가 있을 수 있고, 반면에 진단마다 고유의 정보 또한 존재합니다.
즉 진단이라는 개념으로 여러 진단을 묶을 수 있죠.
예제
제가 예시로 든 상황을 코드로 한번 구현해 봅시다
저는 go 1.21.6을 사용했습니다.
우선 추상 팩토리를 만들어줍니다
diagnostic/define.go
package diagnostic
import "fmt"
type Diagnosis interface {
GetType() string
GetDetails() string
}
// Diagnosis 를 구현하는 Topography
type Topography struct {
Curvature string
Surface string
}
func (t Topography) GetType() string {
return "Topography"
}
func (t Topography) GetDetails() string {
return "Curvature: " + t.Curvature + ", Surface: " + t.Surface
}
// Diagnosis 를 구현하는 AxialLength
type AxialLength struct {
Length float64
}
func (a AxialLength) GetType() string {
return "AxialLength"
}
func (a AxialLength) GetDetails() string {
return "Length: " + fmt.Sprintf("%.2f mm", a.Length)
}
// Diagnosis 를 구현하는 AxialLength
type VisualField struct {
Count int
Success int
Fail int
}
func (v VisualField) GetType() string {
return "VisualField"
}
func (v VisualField) GetDetails() string {
return fmt.Sprintf("Count: %d", v.Count)
}
그 후 팩토리 함수를 만듭니다.
diagnostic/factory.go
package diagnostic
// DiagnosisFactory 추상 팩토리 인터페이스
type DiagnosisFactory interface {
CreateDiagnosis() Diagnosis
}
// TopographyFactory 생성 팩토리 선언
type TopographyFactory struct {
Curvature string
Surface string
}
func (f TopographyFactory) CreateDiagnosis() Diagnosis {
return Topography{
Curvature: f.Curvature,
Surface: f.Surface,
}
}
// AxialLengthFactory 생성 팩토리 선언
type AxialLengthFactory struct {
Length float64
}
func (f AxialLengthFactory) CreateDiagnosis() Diagnosis {
return AxialLength{
Length: f.Length,
}
}
// VisualFieldFactory 생성 팩토리 선언
type VisualFieldFactory struct {
Count int
Success int
Fail int
}
func (f VisualFieldFactory) CreateDiagnosis() Diagnosis {
return VisualField{
Count: f.Count,
Success: f.Success,
Fail: f.Fail,
}
}
이제 main.go에서 아래처럼 사용이 가능합니다.
package main
import (
"design-pattern.com/diagnostic"
"fmt"
)
type Patient struct {
Name string
Diagnoses []diagnostic.Diagnosis
}
func (p *Patient) AddDiagnosis(d diagnostic.Diagnosis) {
p.Diagnoses = append(p.Diagnoses, d)
}
func (p Patient) PrintDiagnoses() {
fmt.Println("Patient Name:", p.Name)
for _, d := range p.Diagnoses {
fmt.Printf("- [%s] %s\n", d.GetType(), d.GetDetails())
}
}
func main() {
// 환자 생성
patient := Patient{Name: "Seung"}
topography := diagnostic.TopographyFactory{
Curvature: "Steep",
Surface: "Smooth",
}.CreateDiagnosis()
axialLength := diagnostic.AxialLengthFactory{
Length: 23.45,
}.CreateDiagnosis()
visualField := diagnostic.VisualFieldFactory{
Count: 12,
Success: 8,
Fail: 4,
}.CreateDiagnosis()
// 환자에 진단을 넣을 때와
patient.AddDiagnosis(topography)
patient.AddDiagnosis(axialLength)
patient.AddDiagnosis(visualField)
// 데이터를 출력할 때 기존 내용을 전혀 몰라도 됨 => 추상 팩토리 사용 이유
patient.PrintDiagnoses()
}
이걸 사용해보면 아래 같은 결과를 봅니다
Patient Name: Seung
- [Topography] Curvature: Steep, Surface: Smooth
- [AxialLength] Length: 23.45 mm
- [VisualField] Count: 12
여태까지의 로직이나 패턴은 요약하면 아래 그림과 같죠
실제 적용은..?
이 패턴을 이전 회사에서 Laravel을 다룰 때도 기존 프로젝트에 적용을 시켜본 적이 있었는데.
이 패턴을 설명해 줘도 같이 일하던 팀원이 필요성을 못 느껴서 적용하기 꺼려하던 경험이 있습니다.
끝내 프로젝트에 적용했지만 "왜 이걸 사용하지"라는 느낌을 지우지 못하더군요
이 부분은 지금은 더 잘 설득할 수 있을 텐데, 제가 부족했죠.
다시 돌아와서 이 패턴은 유지보수에서 엄청난 이점이 있는 건 맞지만
처음 작성하는 코드의 양이 배 이상 늘어나게 되니 작업 속도가 느려지는 경향이 있습니다.
지금 회사에 적용해 볼까 했지만 이미.. 많은 일이 쌓여있기 때문에
나중을 기약할 것 같습니다..
'Go Lang > Study' 카테고리의 다른 글
[GoLang] GraphQL API 만들기 part 1 (라이브러리 탐색) (38) | 2024.03.01 |
---|---|
[GoLang] GoLang 면접 질문 정리 (2) | 2024.02.26 |
[GoLang] 반복문에서 고루틴 돌릴 때 주의점 (0) | 2024.01.19 |
[GoLang] Go에서 동시성이란 (1) | 2024.01.15 |
[GoLang] 데드락, 라이브락, 기아상태 이해하기 (Deadlock, Livelock, Starvation) (1) | 2024.01.02 |