제네릭 구조체
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 70번째.
함수와 마찬가지로 구조체도 타입 매개변수를 가질 수 있습니다. 제네릭 구조체(generic struct)를 사용하면 타입 안전성을 유지하면서 모든 타입에서 작동하는 데이터 구조를 만들 수 있습니다. 이는 스택, 큐 또는 키-값 쌍과 같은 재사용 가능한 컨테이너를 구축하는 데 적합합니다.
제네릭 구조체를 정의하려면, 구조체 이름 뒤에 타입 매개변수를 배치합니다:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}제네릭 함수와 달리, 제네릭 구조체의 인스턴스를 생성할 때는 타입을 명시적으로 지정해야 합니다. Go는 문맥만으로는 타입을 추론할 수 없습니다.
제네릭 구조체는 여러 개의 타입 매개변수를 가질 수 있어, 쌍(pair)이나 맵(map)과 같은 구조에 이상적입니다:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25저장된 타입이 특정 연산을 지원하도록 제약 조건을 적용할 수도 있습니다:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}제네릭 구조체는 Go에서 타입 안전하고 재사용 가능한 데이터 구조를 구축하기 위한 기초를 형성합니다. 다음 레슨에서는 이러한 제네릭 타입에 메서드를 정의하는 방법을 배울 것입니다.
챌린지
쉬움다양한 유형의 아이템을 추적할 수 있는 제네릭 인벤토리 시스템을 구축해 봅시다! 완전한 타입 안전성을 유지하면서 모든 타입과 함께 작동하는 재사용 가능한 컨테이너 구조를 만들게 됩니다.
코드는 두 개의 파일로 구성됩니다:
inventory.go: 제네릭 컨테이너 구조를 정의합니다.두 개의 필드
Name(string)과Data(T 타입)를 가진 제네릭 구조체Item[T any]를 생성하세요. 이는 유연한 타입의 관련 데이터를 가진 모든 아이템을 나타냅니다.Item[T]의 슬라이스를 보유하는 단일 필드Items를 가진 제네릭 구조체Container[T any]를 생성하세요. 이 컨테이너는 동일한 데이터 타입의 여러 아이템을 저장할 수 있습니다.세 개의 필드
Name(string),Quantity(int),Price(T 타입)를 가진 제약 조건이 있는 제네릭 구조체PricedItem[T int | float64]를 생성하세요. 제약 조건은 가격이 항상 숫자임을 보장합니다.빈 Container를 생성하고 그 포인터를 반환하는 함수
NewContainer[T any]() *Container[T]를 구현하세요.이름(string)과 데이터(T)를 받아 Item을 생성하고 이를 컨테이너의 Items 슬라이스에 추가하는
*Container[T]의 메서드Add를 구현하세요.컨테이너에 있는 아이템의 개수를 반환하는
Container[T]의 메서드Count를 구현하세요.main.go: 서로 다른 타입을 사용하여 제네릭 구조체를 시연합니다.아이템 타입(
string,int, 또는priced)을 읽은 다음, 개수와 아이템 상세 정보를 읽습니다.string타입의 경우: 이름과 문자열 데이터 쌍을 읽습니다.Container[string]을 생성하고 모든 아이템을 추가한 다음, 각 아이템을[Name]: [Data]형식으로 출력하고 이어서 총 개수를 출력합니다.int타입의 경우: 이름과 정수 데이터 쌍을 읽습니다.Container[int]를 생성하고 모든 아이템을 추가한 다음, 각 아이템을[Name]: [Data]형식으로 출력하고 이어서 총 개수를 출력합니다.priced타입의 경우: 이름, 수량, 가격(float)의 삼중쌍을 읽습니다.PricedItem[float64]인스턴스를 직접 생성하고 각 아이템을[Name] x[Quantity] @ [Price]형식으로 출력하며, 가격은 소수점 첫째 자리까지 표시합니다.개수 라인은 다음과 같이 출력합니다:
Total items: [count]
다음과 같은 입력이 제공됩니다:
- Line 1: 아이템 타입 (
string,int, 또는priced) - Line 2: 아이템 개수 (정수)
- 이후 라인들: 타입에 따른 아이템 상세 정보
예를 들어, 다음과 같이 주어지면:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paper출력은 다음과 같아야 합니다:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3그리고 다음과 같이 주어지면:
int
2
Apples
50
Oranges
30출력은 다음과 같아야 합니다:
Apples: 50
Oranges: 30
Total items: 2그리고 다음과 같이 주어지면:
priced
2
Widget
10
19.99
Gadget
5
49.50출력은 다음과 같아야 합니다:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2동일한 Container 구조체가 문자열과 정수 모두에서 원활하게 작동하는 반면, PricedItem은 제약 조건을 사용하여 가격에 숫자 타입만 사용할 수 있도록 보장하는 방식에 주목하세요. 이러한 제네릭 구조체의 인스턴스를 생성할 때는 타입 파라미터를 명시적으로 지정해야 합니다.
치트 시트
제네릭 구조체(generic struct)를 사용하면 타입 안전성을 유지하면서 모든 타입에서 작동하는 데이터 구조를 만들 수 있습니다.
제네릭 구조체를 정의하려면 구조체 이름 뒤에 타입 매개변수를 배치합니다:
type Box[T any] struct {
Value T
}제네릭 구조체의 인스턴스를 생성할 때는 타입을 명시적으로 지정해야 합니다(Go는 이를 추론할 수 없습니다):
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}제네릭 구조체는 여러 개의 타입 매개변수를 가질 수 있습니다:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}저장된 타입이 특정 연산을 지원하도록 제약 조건을 적용할 수 있습니다:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}제네릭 구조체는 슬라이스를 포함하여 타입 매개변수를 사용하는 필드를 포함할 수 있습니다:
type Container[T any] struct {
Items []T
}제네릭 구조체에 메서드를 정의하고 제네릭 생성자 함수를 만들 수 있습니다:
func NewContainer[T any]() *Container[T] {
return &Container[T]{Items: []T{}}
}
func (c *Container[T]) Add(item T) {
c.Items = append(c.Items, item)
}직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 항목 타입 읽기
scanner.Scan()
itemType := scanner.Text()
// 개수 읽기
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: NewContainer를 사용하여 Container[string] 생성
// TODO: 'count'개의 항목 읽기 (각 항목은 한 줄에 이름, 다음 줄에 데이터가 있음)
// TODO: 각 항목을 컨테이너에 추가
// TODO: 각 항목을 "[Name]: [Data]" 형식으로 출력
// TODO: Count 메서드를 사용하여 "Total items: [count]" 출력
case "int":
// TODO: NewContainer를 사용하여 Container[int] 생성
// TODO: 'count'개의 항목 읽기 (각 항목은 한 줄에 이름, 다음 줄에 정수 데이터가 있음)
// TODO: 각 항목을 컨테이너에 추가
// TODO: 각 항목을 "[Name]: [Data]" 형식으로 출력
// TODO: Count 메서드를 사용하여 "Total items: [count]" 출력
case "priced":
// TODO: PricedItem[float64] 인스턴스를 담을 슬라이스 생성
// TODO: 'count'개의 항목 읽기 (각 항목은 이름, 수량, 가격이 별도의 줄에 있음)
// TODO: 각 항목에 대해 PricedItem[float64]를 생성하고 슬라이스에 추가
// TODO: 각 항목을 "[Name] x[Quantity] @ [Price]" 형식으로 출력 (가격은 소수점 첫째 자리까지 표시)
// TODO: "Total items: [count]" 출력
}
_ = scanner // 입력을 읽기 위해 scanner 사용
_ = count // 반복문을 위해 count 사용
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서