Golang Options Pattern

1
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개의 댓글

Powered by GraphCDN, the GraphQL CDN