Optional Pattern 을 사용하던 것을 어떻게 Generic으로 바꾸어 표현할 수 있는지 알아보자
Optional Pattern은 함수에 선택적 인자(optional arguments)를 전달하는 방법을 나타내는 디자인 패턴이다. Go는 기본적으로 선택적 인자를 직접적으로 지원하지 않기 때문에, Option 함수 타입을 정의하고 이를 인자로 받는 방식으로 선택적 인자 패턴을 구현한다. 이 패턴으로 구조체의 속성이나 함수의 동작을 유연하게 설정할 수 있다.
예를 들어 나는 repository 에서 GetAll 함수 작성 시 optional pattern을 사용하곤 했다.
type FaqRepository interface {
GetAll(ctx context.Context, options ...FaqQueryOption) (int64, []domain.Faq, error)
GetById(ctx context.Context, id int64) (domain.Faq, error)
}
type FaqQueryOption = func(*FaqQueryOptionFields)
func FaqQueryOptionFunc() FaqQueryOptionFields {
return FaqQueryOptionFields{}
}
type FaqQueryOptionFields struct {
title *string
categoryType *string
}
func (qo FaqQueryOptionFields) WithTitle(title *string) FaqQueryOption {
return func(q *FaqQueryOptionFields) {
q.title = title
}
}
func (qo FaqQueryOptionFields) WithCategoryType(categoryType *string) FaqQueryOption {
return func(q *FaqQueryOptionFields) {
q.categoryType = categoryType
}
}
func (qo FaqQueryOptionFields) Title() *string {
return qo.title
}
func (qo FaqQueryOptionFields) CategoryType() *string {
return qo.categoryType
}
호출부 예시
count, items, err := f.faqRepository.GetAll(ctx,
cdomain.FaqQueryOptionFunc().WithTitle(request.Title),
cdomain.FaqQueryOptionFunc().WithCategoryType(request.CategoryType),
)
if err != nil {
// 예외 처리
}
유연성, 코드 유지보수 용이, 가변적인 인자처리 등에서 장점을 갖고 있다. 남발할 경우 함수가 여러 번 호출되고, 함수 내에서 값이 수정되기 때문에 약간의 런타임 성능 비용이 발생할 수 있음에 유의해야한다.
Go의 제네릭(Generic) 기능은 타입을 매개변수로 사용할 수 있도록 해주는 기능으로, Go 1.18에서 처음 도입되었다. 특징은 다음과 같다.
위에서 언급한 optional pattern 의 예제코드를 generic 으로 바꾸면 다음과 같아진다.
type QueryOption[T any] func(*T)
type FaqQueryOptionFields struct {
title *string
categoryType *string
}
func NewFaqQueryOptionFields() FaqQueryOptionFields {
return FaqQueryOptionFields{}
}
func (qo FaqQueryOptionFields) WithTitle(title *string) QueryOption[FaqQueryOptionFields] {
return func(q *FaqQueryOptionFields) {
q.title = title
}
}
func (qo FaqQueryOptionFields) WithCategoryType(categoryType *string) QueryOption[FaqQueryOptionFields] {
return func(q *FaqQueryOptionFields) {
q.categoryType = categoryType
}
}
옵셔널 패턴(Functional Options Pattern)
제네릭을 활용한 옵셔널 패턴
결국, 상황에 맞게 선택하는 것이 중요하다.
단일 도메인이나 제한된 옵션이 필요한 경우에는 전통적인 옵셔널 패턴이 더 직관적일 수 있고,
여러 도메인에서 공통으로 사용하거나 복잡한 옵션 로직이 필요한 경우에는 제네릭을 적절히 활용해 재사용성과 타입 안전성을 높이는 것이 좋다
https://medium.com/@johnsiilver/go-optional-arguments-in-the-age-of-generics-f8eba23103ad