🏗️ GO 구조체와 인터페이스

📋 목차


1. 구조체 기초

📦 구조체 정의와 생성

구조체란?

구조체는 서로 다른 타입의 데이터를 하나로 묶는 사용자 정의 타입입니다. Go의 객체지향 프로그래밍의 기초가 됩니다.

package main
 
import (
    "fmt"
    "time"
)
 
// 기본 구조체 정의
type Person struct {
    Name    string
    Age     int
    Email   string
    IsActive bool
}
 
// 구조체 필드에 태그 추가 (JSON, 검증 등에 사용)
type User struct {
    ID       int    `json:"id" validate:"required"`
    Username string `json:"username" validate:"required,min=3"`
    Password string `json:"-"`                    // JSON에서 제외
    Email    string `json:"email" validate:"email"`
    CreatedAt time.Time `json:"created_at"`
}
 
// 중첩 구조체
type Address struct {
    Street   string
    City     string
    ZipCode  string
    Country  string
}
 
type Employee struct {
    Person                    // 임베딩 (상속과 유사)
    ID       int             // 직원 ID
    Position string          // 직책
    Salary   float64         // 급여
    Address  Address         // 주소 (컴포지션)
    Manager  *Employee       // 매니저 (자기 참조)
}
 
func structBasics() {
    // 1. 구조체 생성 방법들
    
    // 필드별 초기화
    person1 := Person{
        Name:     "Alice",
        Age:      30,
        Email:    "alice@example.com",
        IsActive: true,
    }
    
    // 순서대로 초기화
    person2 := Person{"Bob", 25, "bob@example.com", false}
    
    // 빈 구조체 생성 후 필드 설정
    var person3 Person
    person3.Name = "Charlie"
    person3.Age = 35
    
    // new()를 사용한 포인터 생성
    person4 := new(Person)
    person4.Name = "Diana"
    person4.Age = 28
    
    fmt.Printf("Person1: %+v\n", person1)
    fmt.Printf("Person2: %+v\n", person2)
    fmt.Printf("Person3: %+v\n", person3)
    fmt.Printf("Person4: %+v\n", *person4)
}

🔄 구조체 조작

func structOperations() {
    // 구조체 생성
    user := User{
        ID:        1,
        Username:  "gopher",
        Email:     "gopher@golang.org",
        CreatedAt: time.Now(),
    }
    
    // 필드 접근 및 수정
    fmt.Printf("Original user: %+v\n", user)
    
    user.Username = "super_gopher"
    user.Email = "super_gopher@golang.org"
    
    fmt.Printf("Modified user: %+v\n", user)
    
    // 구조체 복사 (값 복사)
    userCopy := user
    userCopy.Username = "copy_gopher"
    
    fmt.Printf("Original after copy: %+v\n", user)
    fmt.Printf("Copy: %+v\n", userCopy)
    
    // 구조체 포인터
    userPtr := &user
    userPtr.Email = "ptr_gopher@golang.org"
    
    fmt.Printf("After pointer modification: %+v\n", user)
}

🏢 복잡한 구조체 예제

func complexStructs() {
    // 매니저 생성
    manager := &Employee{
        Person: Person{
            Name:     "John Manager",
            Age:      45,
            Email:    "john.manager@company.com",
            IsActive: true,
        },
        ID:       101,
        Position: "Engineering Manager",
        Salary:   120000.0,
        Address: Address{
            Street:  "123 Manager St",
            City:    "San Francisco",
            ZipCode: "94105",
            Country: "USA",
        },
        Manager: nil, // 최고 관리자
    }
    
    // 직원 생성
    employee := Employee{
        Person: Person{
            Name:     "Jane Developer",
            Age:      28,
            Email:    "jane.dev@company.com",
            IsActive: true,
        },
        ID:       201,
        Position: "Software Engineer",
        Salary:   85000.0,
        Address: Address{
            Street:  "456 Dev Ave",
            City:    "San Francisco", 
            ZipCode: "94107",
            Country: "USA",
        },
        Manager: manager, // 매니저 할당
    }
    
    // 정보 출력
    fmt.Printf("Employee: %s\n", employee.Name)  // 임베딩된 필드 직접 접근
    fmt.Printf("Position: %s\n", employee.Position)
    fmt.Printf("Manager: %s\n", employee.Manager.Name)
    fmt.Printf("Address: %s, %s\n", employee.Address.City, employee.Address.Country)
}

2. 메서드

🎯 메서드 정의

메서드란?

메서드는 특정 타입에 연결된 함수입니다. 리시버(receiver)를 통해 구조체와 연결됩니다.

import (
    "fmt"
    "math"
)
 
type Circle struct {
    X, Y   float64  // 중심 좌표
    Radius float64  // 반지름
}
 
type Rectangle struct {
    Width, Height float64
}
 
// 값 리시버 메서드
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}
 
func (c Circle) Circumference() float64 {
    return 2 * math.Pi * c.Radius
}
 
// 포인터 리시버 메서드 (원본 수정 가능)
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}
 
func (c *Circle) MoveTo(x, y float64) {
    c.X = x
    c.Y = y
}
 
// String() 메서드 (fmt 패키지가 자동으로 호출)
func (c Circle) String() string {
    return fmt.Sprintf("Circle(center: %.1f,%.1f, radius: %.1f)", 
        c.X, c.Y, c.Radius)
}
 
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
 
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}
 
func methodBasics() {
    circle := Circle{X: 0, Y: 0, Radius: 5}
    
    // 값 리시버 메서드 호출
    fmt.Printf("Circle area: %.2f\n", circle.Area())
    fmt.Printf("Circle circumference: %.2f\n", circle.Circumference())
    
    // 포인터 리시버 메서드 호출
    fmt.Printf("Before scaling: %v\n", circle)
    circle.Scale(2.0)  // Go가 자동으로 &circle.Scale(2.0)로 변환
    fmt.Printf("After scaling: %v\n", circle)
    
    circle.MoveTo(10, 10)
    fmt.Printf("After moving: %v\n", circle)
    
    // Rectangle
    rect := Rectangle{Width: 10, Height: 20}
    fmt.Printf("Rectangle area: %.2f\n", rect.Area())
    fmt.Printf("Rectangle perimeter: %.2f\n", rect.Perimeter())
}

🔄 값 리시버 vs 포인터 리시버

type Counter struct {
    value int
}
 
// 값 리시버 - 복사본에서 작업 (원본 변경 안됨)
func (c Counter) IncrementValue() {
    c.value++  // 복사본만 변경됨
}
 
// 포인터 리시버 - 원본에서 작업 (원본 변경됨)
func (c *Counter) IncrementPointer() {
    c.value++  // 원본이 변경됨
}
 
func (c Counter) GetValue() int {
    return c.value
}
 
// 포인터 리시버로 String 구현
func (c *Counter) String() string {
    return fmt.Sprintf("Counter{value: %d}", c.value)
}
 
func receiverTypes() {
    counter := Counter{value: 0}
    
    fmt.Printf("Initial: %v\n", counter)
    
    // 값 리시버 호출 - 원본 변경 안됨
    counter.IncrementValue()
    fmt.Printf("After IncrementValue: %v\n", counter)
    
    // 포인터 리시버 호출 - 원본 변경됨
    counter.IncrementPointer()
    fmt.Printf("After IncrementPointer: %v\n", counter)
    
    // 포인터를 통해서도 호출 가능
    counterPtr := &counter
    counterPtr.IncrementPointer()
    fmt.Printf("After pointer increment: %v\n", counter)
}

3. 인터페이스

🎭 인터페이스 기초

Go의 인터페이스

Go의 인터페이스는 암시적입니다. 명시적으로 구현을 선언할 필요 없이, 메서드만 구현하면 자동으로 인터페이스를 만족합니다.

// Shape 인터페이스 정의
type Shape interface {
    Area() float64
    Perimeter() float64
}
 
// Drawable 인터페이스
type Drawable interface {
    Draw() string
}
 
// 복합 인터페이스 (인터페이스 임베딩)
type DrawableShape interface {
    Shape      // Shape 인터페이스 임베딩
    Drawable   // Drawable 인터페이스 임베딩
}
 
// Circle의 Drawable 인터페이스 구현
func (c Circle) Perimeter() float64 {
    return c.Circumference()  // 기존 메서드 재사용
}
 
func (c Circle) Draw() string {
    return fmt.Sprintf("Drawing a circle with radius %.1f at (%.1f, %.1f)", 
        c.Radius, c.X, c.Y)
}
 
// Rectangle의 Drawable 인터페이스 구현
func (r Rectangle) Draw() string {
    return fmt.Sprintf("Drawing a rectangle %.1fx%.1f", r.Width, r.Height)
}
 
// Triangle 새로운 도형
type Triangle struct {
    A, B, C float64  // 세 변의 길이
}
 
func (t Triangle) Area() float64 {
    s := (t.A + t.B + t.C) / 2  // 반둘레
    return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))  // 헤론의 공식
}
 
func (t Triangle) Perimeter() float64 {
    return t.A + t.B + t.C
}
 
func (t Triangle) Draw() string {
    return fmt.Sprintf("Drawing a triangle with sides %.1f, %.1f, %.1f", 
        t.A, t.B, t.C)
}
 
func interfaceBasics() {
    // 다양한 도형 생성
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 10, Height: 20},
        Triangle{A: 3, B: 4, C: 5},
    }
    
    // 인터페이스를 통한 폴리모피즘
    for i, shape := range shapes {
        fmt.Printf("Shape %d:\n", i+1)
        fmt.Printf("  Area: %.2f\n", shape.Area())
        fmt.Printf("  Perimeter: %.2f\n", shape.Perimeter())
        
        // 타입 assertion을 통한 구체 타입 확인
        switch s := shape.(type) {
        case Circle:
            fmt.Printf("  Type: Circle (radius: %.1f)\n", s.Radius)
        case Rectangle:
            fmt.Printf("  Type: Rectangle (%.1fx%.1f)\n", s.Width, s.Height)
        case Triangle:
            fmt.Printf("  Type: Triangle (sides: %.1f, %.1f, %.1f)\n", s.A, s.B, s.C)
        }
        
        // Drawable 인터페이스 체크
        if drawable, ok := shape.(Drawable); ok {
            fmt.Printf("  %s\n", drawable.Draw())
        }
        
        fmt.Println()
    }
}

🔍 인터페이스 고급 활용

import (
    "io"
    "strings"
    "encoding/json"
)
 
// 빈 인터페이스 (모든 타입을 담을 수 있음)
func handleAny(value interface{}) {
    fmt.Printf("Value: %v, Type: %T\n", value, value)
    
    // 타입 스위치
    switch v := value.(type) {
    case int:
        fmt.Printf("  Integer: %d\n", v)
    case string:
        fmt.Printf("  String: %s\n", v)
    case []int:
        fmt.Printf("  Int slice: %v\n", v)
    case Shape:
        fmt.Printf("  Shape with area: %.2f\n", v.Area())
    default:
        fmt.Printf("  Unknown type: %T\n", v)
    }
}
 
// 인터페이스 조합
type Writer interface {
    Write([]byte) (int, error)
}
 
type Reader interface {
    Read([]byte) (int, error)
}
 
// io.ReadWriter는 Reader와 Writer를 모두 구현
type ReadWriter interface {
    Reader
    Writer
}
 
// 커스텀 Writer 구현
type StringWriter struct {
    data strings.Builder
}
 
func (sw *StringWriter) Write(p []byte) (n int, err error) {
    return sw.data.Write(p)
}
 
func (sw *StringWriter) String() string {
    return sw.data.String()
}
 
func interfaceAdvanced() {
    // 빈 인터페이스 사용
    values := []interface{}{
        42,
        "Hello",
        []int{1, 2, 3},
        Circle{Radius: 3},
        Rectangle{Width: 5, Height: 10},
    }
    
    for _, val := range values {
        handleAny(val)
        fmt.Println()
    }
    
    // Writer 인터페이스 사용
    var writer StringWriter
    
    // io.WriteString은 Writer 인터페이스를 받음
    io.WriteString(&writer, "Hello, ")
    io.WriteString(&writer, "Go Interface!")
    
    fmt.Printf("Writer result: %s\n", writer.String())
}

🎯 인터페이스 실전 패턴

// Strategy 패턴
type PaymentProcessor interface {
    ProcessPayment(amount float64) error
    GetFee(amount float64) float64
}
 
type CreditCardProcessor struct {
    CardNumber string
}
 
func (cc CreditCardProcessor) ProcessPayment(amount float64) error {
    fmt.Printf("Processing credit card payment: $%.2f\n", amount)
    return nil
}
 
func (cc CreditCardProcessor) GetFee(amount float64) float64 {
    return amount * 0.03  // 3% 수수료
}
 
type PayPalProcessor struct {
    Email string
}
 
func (pp PayPalProcessor) ProcessPayment(amount float64) error {
    fmt.Printf("Processing PayPal payment: $%.2f\n", amount)
    return nil
}
 
func (pp PayPalProcessor) GetFee(amount float64) float64 {
    return amount * 0.025  // 2.5% 수수료
}
 
// 결제 서비스
type PaymentService struct {
    processor PaymentProcessor
}
 
func NewPaymentService(processor PaymentProcessor) *PaymentService {
    return &PaymentService{processor: processor}
}
 
func (ps *PaymentService) ProcessOrder(amount float64) error {
    fee := ps.processor.GetFee(amount)
    totalAmount := amount + fee
    
    fmt.Printf("Order amount: $%.2f, Fee: $%.2f, Total: $%.2f\n", 
        amount, fee, totalAmount)
    
    return ps.processor.ProcessPayment(totalAmount)
}
 
func strategyPattern() {
    // 신용카드 결제
    ccProcessor := CreditCardProcessor{CardNumber: "**** **** **** 1234"}
    ccService := NewPaymentService(ccProcessor)
    ccService.ProcessOrder(100.00)
    
    fmt.Println()
    
    // PayPal 결제
    ppProcessor := PayPalProcessor{Email: "user@example.com"}
    ppService := NewPaymentService(ppProcessor)
    ppService.ProcessOrder(100.00)
}

4. 임베딩과 컴포지션

🧩 구조체 임베딩

임베딩

Go는 상속을 지원하지 않지만, 구조체 임베딩을 통해 유사한 효과를 얻을 수 있습니다.

// 기본 엔티티
type BaseEntity struct {
    ID        int
    CreatedAt time.Time
    UpdatedAt time.Time
}
 
func (be *BaseEntity) SetID(id int) {
    be.ID = id
}
 
func (be *BaseEntity) GetID() int {
    return be.ID
}
 
func (be *BaseEntity) Touch() {
    be.UpdatedAt = time.Now()
}
 
// 사용자 엔티티 (BaseEntity 임베딩)
type User struct {
    BaseEntity        // 임베딩 (익명 필드)
    Username   string
    Email      string
    IsActive   bool
}
 
func (u *User) Activate() {
    u.IsActive = true
    u.Touch()  // 임베딩된 메서드 호출
}
 
func (u *User) Deactivate() {
    u.IsActive = false
    u.Touch()
}
 
// 제품 엔티티 (BaseEntity 임베딩)
type Product struct {
    BaseEntity
    Name        string
    Price       float64
    InStock     bool
    Description string
}
 
func (p *Product) UpdatePrice(newPrice float64) {
    p.Price = newPrice
    p.Touch()  // 임베딩된 메서드 호출
}
 
func embeddingExample() {
    // 사용자 생성
    user := User{
        BaseEntity: BaseEntity{
            ID:        1,
            CreatedAt: time.Now(),
            UpdatedAt: time.Now(),
        },
        Username: "gopher",
        Email:    "gopher@example.com",
        IsActive: false,
    }
    
    fmt.Printf("User before activation: %+v\n", user)
    
    // 임베딩된 메서드와 추가된 메서드 사용
    user.Activate()
    fmt.Printf("User after activation: %+v\n", user)
    
    // 임베딩된 필드 직접 접근
    fmt.Printf("User ID: %d\n", user.ID)  // user.BaseEntity.ID와 같음
    fmt.Printf("User ID (method): %d\n", user.GetID())
    
    // 제품 생성
    product := Product{
        BaseEntity: BaseEntity{
            ID:        101,
            CreatedAt: time.Now(),
            UpdatedAt: time.Now(),
        },
        Name:        "Go Programming Book",
        Price:       29.99,
        InStock:     true,
        Description: "Learn Go programming",
    }
    
    fmt.Printf("\nProduct before price update: %+v\n", product)
    product.UpdatePrice(24.99)
    fmt.Printf("Product after price update: %+v\n", product)
}

🔄 메서드 오버라이드

// 로깅 기능이 있는 기본 구조체
type Logger struct {
    Name string
}
 
func (l Logger) Log(message string) {
    fmt.Printf("[%s] %s\n", l.Name, message)
}
 
func (l Logger) Info(message string) {
    l.Log("INFO: " + message)
}
 
// 데이터베이스 로거 (Logger 임베딩)
type DatabaseLogger struct {
    Logger
    Connection string
}
 
// Log 메서드 오버라이드
func (dl DatabaseLogger) Log(message string) {
    fmt.Printf("[DB:%s] [%s] %s\n", dl.Connection, dl.Name, message)
}
 
// 파일 로거 (Logger 임베딩)
type FileLogger struct {
    Logger
    FilePath string
}
 
// Log 메서드 오버라이드
func (fl FileLogger) Log(message string) {
    fmt.Printf("[FILE:%s] [%s] %s\n", fl.FilePath, fl.Name, message)
}
 
func methodOverride() {
    // 기본 로거
    basicLogger := Logger{Name: "BasicLogger"}
    basicLogger.Log("Basic log message")
    basicLogger.Info("Basic info message")
    
    fmt.Println()
    
    // 데이터베이스 로거
    dbLogger := DatabaseLogger{
        Logger:     Logger{Name: "DBLogger"},
        Connection: "postgresql://localhost:5432",
    }
    dbLogger.Log("Database log message")      // 오버라이드된 메서드
    dbLogger.Info("Database info message")    // 임베딩된 메서드가 오버라이드된 Log 호출
    
    fmt.Println()
    
    // 파일 로거
    fileLogger := FileLogger{
        Logger:   Logger{Name: "FileLogger"},
        FilePath: "/var/log/app.log",
    }
    fileLogger.Log("File log message")        // 오버라이드된 메서드
    fileLogger.Info("File info message")      // 임베딩된 메서드가 오버라이드된 Log 호출
}

🎯 실습 예제

📝 연습 문제 1: 은행 계좌 시스템

package main
 
import (
    "fmt"
    "errors"
    "time"
)
 
// 계좌 인터페이스
type Account interface {
    Deposit(amount float64) error
    Withdraw(amount float64) error
    GetBalance() float64
    GetAccountInfo() string
}
 
// 기본 계좌
type BaseAccount struct {
    AccountNumber string
    Owner         string
    Balance       float64
    CreatedAt     time.Time
}
 
func (ba *BaseAccount) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("deposit amount must be positive")
    }
    ba.Balance += amount
    return nil
}
 
func (ba *BaseAccount) GetBalance() float64 {
    return ba.Balance
}
 
func (ba *BaseAccount) GetAccountInfo() string {
    return fmt.Sprintf("Account: %s, Owner: %s, Balance: $%.2f", 
        ba.AccountNumber, ba.Owner, ba.Balance)
}
 
// 저축 계좌 (BaseAccount 임베딩)
type SavingsAccount struct {
    BaseAccount
    InterestRate float64
}
 
func (sa *SavingsAccount) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("withdrawal amount must be positive")
    }
    if sa.Balance < amount {
        return errors.New("insufficient funds")
    }
    sa.Balance -= amount
    return nil
}
 
func (sa *SavingsAccount) AddInterest() {
    interest := sa.Balance * sa.InterestRate / 100
    sa.Balance += interest
    fmt.Printf("Interest added: $%.2f\n", interest)
}
 
// 당좌 계좌 (BaseAccount 임베딩)
type CheckingAccount struct {
    BaseAccount
    OverdraftLimit float64
}
 
func (ca *CheckingAccount) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("withdrawal amount must be positive")
    }
    if sa.Balance + ca.OverdraftLimit < amount {
        return errors.New("overdraft limit exceeded")
    }
    ca.Balance -= amount
    return nil
}
 
func (ca *CheckingAccount) GetOverdraftUsage() float64 {
    if ca.Balance >= 0 {
        return 0
    }
    return -ca.Balance
}
 
// 은행 시스템
type Bank struct {
    accounts map[string]Account
}
 
func NewBank() *Bank {
    return &Bank{
        accounts: make(map[string]Account),
    }
}
 
func (b *Bank) AddAccount(account Account) {
    accountInfo := account.GetAccountInfo()
    // 계좌 번호 추출 (실제로는 더 정교한 방법 필요)
    b.accounts[accountInfo] = account
}
 
func (b *Bank) ProcessTransaction(account Account, transactionType string, amount float64) {
    fmt.Printf("\n--- %s Transaction ---\n", transactionType)
    fmt.Printf("Before: %s\n", account.GetAccountInfo())
    
    var err error
    switch transactionType {
    case "Deposit":
        err = account.Deposit(amount)
    case "Withdraw":
        err = account.Withdraw(amount)
    }
    
    if err != nil {
        fmt.Printf("Transaction failed: %v\n", err)
    } else {
        fmt.Printf("Transaction successful: $%.2f\n", amount)
    }
    
    fmt.Printf("After: %s\n", account.GetAccountInfo())
}
 
func bankSystemExample() {
    bank := NewBank()
    
    // 저축 계좌 생성
    savings := &SavingsAccount{
        BaseAccount: BaseAccount{
            AccountNumber: "SAV001",
            Owner:         "Alice",
            Balance:       1000.0,
            CreatedAt:     time.Now(),
        },
        InterestRate: 2.5,
    }
    
    // 당좌 계좌 생성
    checking := &CheckingAccount{
        BaseAccount: BaseAccount{
            AccountNumber: "CHK001",
            Owner:         "Bob",
            Balance:       500.0,
            CreatedAt:     time.Now(),
        },
        OverdraftLimit: 200.0,
    }
    
    bank.AddAccount(savings)
    bank.AddAccount(checking)
    
    // 거래 처리
    bank.ProcessTransaction(savings, "Deposit", 200.0)
    bank.ProcessTransaction(savings, "Withdraw", 150.0)
    
    bank.ProcessTransaction(checking, "Deposit", 100.0)
    bank.ProcessTransaction(checking, "Withdraw", 800.0)  // 한도 초과 테스트
    bank.ProcessTransaction(checking, "Withdraw", 650.0)  // 성공
    
    // 이자 추가 (저축 계좌만)
    fmt.Println("\n--- Interest Calculation ---")
    savings.AddInterest()
    fmt.Printf("After interest: %s\n", savings.GetAccountInfo())
    
    // 오버드래프트 사용량 확인 (당좌 계좌만)
    fmt.Printf("Overdraft usage: $%.2f\n", checking.GetOverdraftUsage())
}

📝 연습 문제 2: 게임 캐릭터 시스템

// 캐릭터 인터페이스
type Character interface {
    Attack() int
    TakeDamage(damage int)
    IsAlive() bool
    GetStatus() string
}
 
// 기본 캐릭터
type BaseCharacter struct {
    Name      string
    HP        int
    MaxHP     int
    AttackPower int
}
 
func (bc *BaseCharacter) TakeDamage(damage int) {
    bc.HP -= damage
    if bc.HP < 0 {
        bc.HP = 0
    }
}
 
func (bc *BaseCharacter) IsAlive() bool {
    return bc.HP > 0
}
 
func (bc *BaseCharacter) GetStatus() string {
    return fmt.Sprintf("%s: HP %d/%d", bc.Name, bc.HP, bc.MaxHP)
}
 
// 전사 (BaseCharacter 임베딩)
type Warrior struct {
    BaseCharacter
    Armor int
}
 
func (w *Warrior) Attack() int {
    return w.AttackPower + 5  // 전사는 추가 공격력
}
 
func (w *Warrior) TakeDamage(damage int) {
    reducedDamage := damage - w.Armor
    if reducedDamage < 0 {
        reducedDamage = 0
    }
    w.BaseCharacter.TakeDamage(reducedDamage)
}
 
// 마법사 (BaseCharacter 임베딩)
type Mage struct {
    BaseCharacter
    Mana    int
    MaxMana int
}
 
func (m *Mage) Attack() int {
    return w.AttackPower
}
 
func (m *Mage) CastSpell() int {
    if m.Mana >= 10 {
        m.Mana -= 10
        return m.AttackPower * 2  // 마법은 2배 데미지
    }
    return m.Attack()  // 마나가 없으면 일반 공격
}
 
func gameExample() {
    warrior := &Warrior{
        BaseCharacter: BaseCharacter{
            Name:        "Knight",
            HP:          100,
            MaxHP:       100,
            AttackPower: 20,
        },
        Armor: 5,
    }
    
    mage := &Mage{
        BaseCharacter: BaseCharacter{
            Name:        "Wizard",
            HP:          70,
            MaxHP:       70,
            AttackPower: 15,
        },
        Mana:    50,
        MaxMana: 50,
    }
    
    // 전투 시뮬레이션
    fmt.Println("=== Battle Start ===")
    fmt.Printf("%s vs %s\n", warrior.Name, mage.Name)
    
    round := 1
    for warrior.IsAlive() && mage.IsAlive() {
        fmt.Printf("\n--- Round %d ---\n", round)
        
        // 전사 공격
        if warrior.IsAlive() {
            damage := warrior.Attack()
            mage.TakeDamage(damage)
            fmt.Printf("%s attacks for %d damage\n", warrior.Name, damage)
            fmt.Printf("%s\n", mage.GetStatus())
        }
        
        // 마법사 공격
        if mage.IsAlive() {
            damage := mage.CastSpell()
            warrior.TakeDamage(damage)
            fmt.Printf("%s casts spell for %d damage\n", mage.Name, damage)
            fmt.Printf("%s\n", warrior.GetStatus())
        }
        
        round++
        if round > 10 {  // 무한 루프 방지
            break
        }
    }
    
    fmt.Println("\n=== Battle End ===")
    if warrior.IsAlive() {
        fmt.Printf("%s wins!\n", warrior.Name)
    } else if mage.IsAlive() {
        fmt.Printf("%s wins!\n", mage.Name)
    } else {
        fmt.Println("Draw!")
    }
}

✅ 체크리스트

구조체

  • 구조체 정의와 초기화
  • 구조체 태그 활용
  • 중첩 구조체와 자기 참조
  • 구조체 복사와 포인터

메서드

  • 값 리시버 vs 포인터 리시버
  • 메서드 정의와 호출
  • String() 메서드 구현
  • 메서드 체이닝 패턴

인터페이스

  • 인터페이스 정의와 구현
  • 암시적 인터페이스 이해
  • 빈 인터페이스 활용
  • 타입 assertion과 타입 스위치

임베딩과 컴포지션

  • 구조체 임베딩 이해
  • 메서드 오버라이드
  • 컴포지션 vs 상속
  • 인터페이스 임베딩

다음 단계

구조체와 인터페이스를 익혔다면 GO 동시성 - 고루틴과 채널로 넘어가세요!