Golang Options Pattern

0
post-thumbnail

Go 생성자

Go에서는 Python __init__() 과 같은 명시적인 생성자가 없습니다. 그렇기 때문에 일반적으로 객체를 생성할 때는 함수의 네이밍을 New + <StructName>로 구성하면서 이 함수가 생성자임을 나타냅니다. 아래는 하나의 객체를 생성하는 간단한 예시입니다. 필요한 매개변수를 함수에 직접 전달하는 방식으로 객체의 속성의 값을 설정하고 생성할 수 있습니다.

type Person struct {
	FirstName string
	LastName  string
}

func NewPerson(firstName, lastName string) *Person {
	return &Person{
		FirstName: firstName,
		LastName:  lastName,
	}
}

func main() {
	person := NewPerson("John", "Doe")
	fmt.Println(person)
}

간단한 객체를 생성할 때는 위와 같이 직접 전달하여 생성하는 것이 직관적이고 간결합니다. 하지만 Struct에 속성이 늘어난다면 어떨까요 ? 간단한 예시입니다.

type Person struct {
	FirstName string
	LastName  string
	Age       int
	Gender    string
	Address   string
}

func NewPerson(firstName, lastName, gender, address string, age int) *Person {
	return &Person{
		FirstName: firstName,
		LastName:  lastName,
		Age:       age,
		Gender:    gender,
		Address:   address,
	}
}

func main() {
	person := NewPerson("John", "Doe", "male", "New York", 25)
	fmt.Println(person)
}

물론 위와 같이 모든 매개변수를 전달하여 생성할 수도 있지만, 우리는 몇 가지 질문이 떠오릅니다.

  • 전달해야하는 매개변수가 더 증가한다면 ?
  • 일부 속성으로만(선택적으로) 객체를 생성하고 싶을 때는 ?
  • 구조체의 새로운 속성이 추가 될 때마다 모든 객체의 생성부를 수정 해야하지 않을까 ?
  • 그 때마다 떨어지는 가독성은 ?

등등 여러 고민에 맞닥뜨리게 됩니다. 여기서 이러한 문제를 보완하는 것이 Options Pattern 입니다.

Options Pattern

Options Pattern: 객체 생성 시, 필요한 설정만 선택적으로 사용하여 객체를 생성하는 패턴

선택적으로 사용한다는 것은 Setter를 구현하고 이를 사용하는 것입니다. Go에서 구조체의 속성을 설정하기 위한 Setter 함수의 네이밍은 일반적으로 With~ 혹은 Set~ 의 네이밍을 주로 사용합니다. (Options Pattern에서는 좀 더 With~ 네이밍을 사용하는 것 같네요 .. ?)

func WithFirstName(firstName string) func(*Person) {
	return func(p *Person) {
		p.FirstName = firstName
	}
}

func WithLastName(lastName string) func(*Person) {
	return func(p *Person) {
		p.LastName = lastName
	}
}

func WithAge(age int) func(*Person) {
	return func(p *Person) {
		p.Age = age
	}
}

위의 함수들의 시그니처를 보게되면, With + <속성>의 네이밍을 가지고 있고, 속성에 설정할 값을 매개변수로 받고 있습니다. 응답 타입은 포인터 구조체를 매개변수로 받는 함수이고, 그 함수를 보게되면, 구조체의 특정 속성을 매개변수로 설정하고 있습니다.

그리고 이를 호출하고 활용하는 방법은 아래와 같습니다. 생성자 함수를 보게 되면 이전에는 각 속성을 직접적인 매개변수로 받았지만, Options Pattern을 적용한 이후 Setter 함수들을 매개변수로 받으면서 내부에서 그 함수들을 순회하고, 최종 객체를 반환합니다.

func NewPerson(opts ...func(*Person)) *Person {
	p := &Person{}
	for _, opt := range opts {
		opt(p)
	}
	return p
}

func main() {
	person := NewPerson(
		WithFirstName("Jin"),
		WithLastName("Lee"),
		WithAge(30),
	)
	fmt.Println(person)
}

Options Pattern을 사용하면,

  • 사용자는 필요한 옵션만 선택사여 전달하면 되니 매서드의 호출이 보다 유연해집니다. 만약 사용자가 특정 옵션을 전달하지 않는다면 기본값이 사용됩니다.
  • 새로운 옵션이 추가되더라도 기존 함수의 시그니처를 변경하지 않고도 새로운 옵션만 추가하여 사용 할 수 있습니다. 코드의 확장성과 유지보수성이 증가했습니다.
  • 옵션들을 호출하는 매서드의 네이밍이 직관적이다보니 객체의 어떤 속성 값이 설정되는지 쉽게 이해가 되어 가독성을 향상되었습니다.

이러한 장점들로 객체의 복잡한 설정이 필요한 경우 자주 사용되곤합니다.

0개의 댓글