Menu
Coddy logo textTech

함수형 옵션 패턴

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 79번째.

많은 선택적 구성 필드가 있는 구조체를 생성할 때 생성자 function은 다루기 어려워질 수 있습니다. Functional Options Pattern은 객체를 구성하는 function을 사용하여 이 문제를 우아하게 해결합니다.

여러 선택적 설정이 있는 서버 구조체를 생각해 보세요. 많은 매개변수를 사용하는 생성자 대신, 옵션 타입과 옵션을 반환하는 함수를 정의합니다.

type Server struct {
    host    string
    port    int
    timeout int
}

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) {
        s.port = port
    }
}

func WithTimeout(timeout int) Option {
    return func(s *Server) {
        s.timeout = timeout
    }
}

생성자는 가변 인수 옵션 목록을 받아 각각을 적용합니다:

func NewServer(host string, opts ...Option) *Server {
    s := &Server{
        host:    host,
        port:    8080,    // 기본
        timeout: 30,      // 기본
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

이제 서버를 생성하는 작업이 읽기 쉽고 유연해집니다:

// 기본값 사용
s1 := NewServer("localhost")

// 특정 옵션 사용자 지정
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))

이 패턴이 뛰어난 이유는 호출자가 필요한 것만 지정하고, defaults가 한곳에서 명확하게 정의되며, 새로운 옵션을 추가해도 기존 코드가 깨지지 않기 때문입니다. 이 패턴은 grpczap과 같은 Go 라이브러리에서 널리 사용됩니다.

challenge icon

챌린지

쉬움

Functional Options Pattern을 사용하여 구성 가능한 database connection을 만들어 봅시다! 이 패턴은 합리적인 기본값을 가진 선택적 설정이 많을 때 특히 유용하며, 이는 database connection에 정확히 필요한 방식입니다.

코드를 두 개의 파일로 구성합니다:

  • database.go: database connection type과 option functions를 Define합니다.

    다음의 unexported fields를 가진 DBConnection struct를 Create합니다:

    • host (string)
    • port (int)
    • username (string)
    • password (string)
    • maxConnections (int)
    • timeout (int) - 초 단위

    *DBConnection을 modifies하는 function으로 Option type을 Define합니다.

    Option을 반환하는 다음 option functions를 Create합니다:

    • WithPort(port int)
    • WithCredentials(username, password string)
    • WithMaxConnections(max int)
    • WithTimeout(seconds int)

    *DBConnection을 반환하는 constructor NewDBConnection(host string, opts ...Option)을 Create합니다. 다음 defaults를 설정합니다: port 5432, username "admin", password "secret", maxConnections 10, timeout 30. defaults를 설정한 후 제공된 모든 options를 Apply합니다.

    connection info를 다음 Format으로 반환하는 ConnectionString() method를 Add합니다:

    [username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)
  • main.go: 다양한 구성으로 database connections를 만듭니다.

    host를 읽은 다음, Apply할 options의 수를 읽습니다. 각 option에 대해 option type과 해당 value(s)를 읽습니다:

    • port 다음에 port number
    • credentials 다음에 username과 password (두 줄)
    • maxconn 다음에 max connections number
    • timeout 다음에 초 단위의 timeout

    지정된 options로 DBConnection을 Create하고 connection string을 출력합니다.

다음 inputs가 제공됩니다:

  • Line 1: Host name
  • Line 2: Number of options
  • Following lines: Option type and value(s)

예를 들어, 다음과 같이 주어졌을 때:

localhost
0

출력은 다음과 같아야 합니다:

admin:secret@localhost:5432 (max:10, timeout:30s)

그리고 다음과 같이 주어졌을 때:

db.example.com
2
port
3306
credentials
root
mypassword

출력은 다음과 같아야 합니다:

root:mypassword@db.example.com:3306 (max:10, timeout:30s)

그리고 다음과 같이 주어졌을 때:

production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60

출력은 다음과 같아야 합니다:

dbuser:securepass123@production.server:5433 (max:50, timeout:60s)

호출자는 필요한 options만 지정하고, 지정하지 않은 settings에는 합리적인 defaults가 사용된다는 점에 주목하세요. 이것이 바로 Functional Options Pattern의 우아함입니다!

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 호스트 읽기
	host, _ := reader.ReadString('\n')
	host = strings.TrimSpace(host)

	// 옵션 수 읽기
	numOptionsStr, _ := reader.ReadString('\n')
	numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))

	// 옵션 수집
	var opts []Option

	for i := 0; i < numOptions; i++ {
		optionType, _ := reader.ReadString('\n')
		optionType = strings.TrimSpace(optionType)

		switch optionType {
		case "port":
			portStr, _ := reader.ReadString('\n')
			port, _ := strconv.Atoi(strings.TrimSpace(portStr))
			// TODO: WithPort 옵션을 opts에 추가
			_ = port // 구현 시 이 줄을 제거하세요

		case "credentials":
			username, _ := reader.ReadString('\n')
			username = strings.TrimSpace(username)
			password, _ := reader.ReadString('\n')
			password = strings.TrimSpace(password)
			// TODO: WithCredentials 옵션을 opts에 추가
			_, _ = username, password // 구현 시 이 줄을 제거하세요

		case "maxconn":
			maxStr, _ := reader.ReadString('\n')
			maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
			// TODO: WithMaxConnections 옵션을 opts에 추가
			_ = maxConn // 구현 시 이 줄을 제거하세요

		case "timeout":
			timeoutStr, _ := reader.ReadString('\n')
			timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
			// TODO: WithTimeout 옵션을 opts에 추가
			_ = timeout // 구현 시 이 줄을 제거하세요
		}
	}

	// TODO: host와 opts로 NewDBConnection을 사용하여 DBConnection 생성
	// TODO: ConnectionString() 메서드를 사용하여 연결 문자열 출력

	_ = opts // 구현 시 이 줄을 제거하세요
	fmt.Println("TODO: Print connection string here")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 Go 컴파일러