0Pricing
Go Academy · 강의

인터페이스 조합과 모범 사례

작은 인터페이스와 의존성 역전

인터페이스 조합과 모범 사례은(는) CoddyKit의 무료 Go Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Go Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Go Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

인터페이스 조합

Go 인터페이스는 다른 인터페이스를 임베드하여 더 큰 인터페이스를 조합할 수 있습니다:

package main
import "io"

// io.ReadWriter composes Reader and Writer
type ReadWriter interface {
    io.Reader
    io.Writer
}

// Your own composition:
type Processor interface {
    io.Reader
    Process() error
    io.Closer
}

func main() { _ = (*Processor)(nil) }

작은 인터페이스 원칙

가장 강력한 Go 인터페이스에는 메서드가 한두 개만 있습니다. 그러면 해당 인터페이스를 충족할 수 있는 타입의 수가 최대화됩니다:

  • io.Reader — 메서드 1개
  • io.Writer — 메서드 1개
  • error — 메서드 1개
  • fmt.Stringer — 메서드 1개

큰 인터페이스는 모의 객체를 만들고 테스트하기가 더 어렵습니다.

의존성 역전

구체 타입 대신 함수 매개변수로 인터페이스를 받으세요. 이렇게 하면 코드가 특정 구현과 분리됩니다:

package main
import "fmt"

type Store interface {
    Get(key string) (string, bool)
    Set(key, val string)
}

func cache(s Store, key, value string) {
    if _, ok := s.Get(key); !ok {
        s.Set(key, value)
    }
}

type MapStore struct{ m map[string]string }
func (ms *MapStore) Get(k string) (string, bool) { v, ok := ms.m[k]; return v, ok }
func (ms *MapStore) Set(k, v string) { ms.m[k] = v }

func main() {
    s := &MapStore{m: map[string]string{}}
    cache(s, "x", "hello")
    v, _ := s.Get("x")
    fmt.Println(v)
}

인터페이스 분리

큰 인터페이스를 더 작고 목적이 분명한 인터페이스로 나누세요. 클라이언트는 자신이 사용하는 메서드에만 의존합니다:

package main

// Bad: large interface forces all methods
type BigStorage interface {
    Read(key string) string
    Write(key, val string)
    Delete(key string)
    List() []string
    Backup() error
}

// Good: small, focused interfaces
type Reader interface{ Read(key string) string }
type Writer interface{ Write(key, val string) }
type Lister interface{ List() []string }

func readAll(r Reader) {} // only needs Read

func main() { _ = readAll }

구체 타입 반환

생성자는 인터페이스가 아니라 구체 타입을 반환해야 합니다. 호출자는 언제든지 구체 타입을 인터페이스 변수에 할당할 수 있습니다:

package main

type Logger interface{ Log(string) }
type ConsoleLogger struct{}
func (c *ConsoleLogger) Log(s string) {}

// Good: return concrete *ConsoleLogger
func NewConsoleLogger() *ConsoleLogger { return &ConsoleLogger{} }

// Avoid: return Logger (hides the concrete type)
// func NewConsoleLogger() Logger { return &ConsoleLogger{} }

func main() { _ = NewConsoleLogger() }

인터페이스 남용

“혹시 모르니” 모든 타입에 인터페이스를 만들지는 마세요. 다음과 같은 경우에만 인터페이스를 만드세요:

  • 현재 또는 곧 여러 구현이 있을 때
  • 테스트에서 의존성을 모의 객체로 대체해야 할 때
  • 라이브러리를 만들면서 호출자를 분리하고 싶을 때

확장을 위한 인터페이스 임베딩

인터페이스를 임베드하여 기본값이 있는 선택적 메서드를 추가하세요:

package main
import "fmt"

type Handler interface{ Handle(msg string) }
type NamedHandler interface {
    Handler
    Name() string
}

func dispatch(h Handler) {
    if nh, ok := h.(NamedHandler); ok {
        fmt.Printf("[%s] ", nh.Name())
    }
    h.Handle("event")
}

type MyHandler struct{}
func (m MyHandler) Handle(msg string)  { fmt.Println(msg) }
func (m MyHandler) Name() string       { return "my" }

func main() { dispatch(MyHandler{}) }

인터페이스를 사용한 모의 객체

의존성을 인터페이스로 정의하고, 테스트에서 모의 객체를 주입하세요:

package main
import "fmt"

type Clock interface{ Now() int64 }

type MockClock struct{ T int64 }
func (m MockClock) Now() int64 { return m.T }

func isExpired(c Clock, expiresAt int64) bool {
    return c.Now() > expiresAt
}

func main() {
    mc := MockClock{T: 1000}
    fmt.Println(isExpired(mc, 500))  // true
    fmt.Println(isExpired(mc, 1500)) // false
}

재사용을 위한 인터페이스 임베딩과 구조체 임베딩 비교

API 조합에는 인터페이스 임베딩을 사용하고 코드 재사용에는 구조체 임베딩을 사용하세요. 두 방식은 목적이 다르므로 혼동해서는 안 됩니다.

컴파일 시 인터페이스 충족 여부 확인

빈 식별자를 사용한 컴파일 시점 확인으로 타입이 인터페이스를 충족하는지 검증하세요:

package main

type Doer interface{ Do() }
type MyType struct{}
func (m *MyType) Do() {}

// Compile-time check — no runtime cost
var _ Doer = (*MyType)(nil)

func main() {}

빠른 확인

Go에서 새 인터페이스는 언제 정의해야 하나요?

복습: 인터페이스 모범 사례

핵심 내용:

  • 인터페이스를 작게 유지하세요 — 메서드는 한두 개
  • 더 작은 인터페이스로 인터페이스를 조합하세요
  • 인터페이스를 받고 구체 타입을 반환하세요
  • 테스트 가능성과 여러 구현을 위해 인터페이스를 만드세요
  • 빈 할당을 사용한 컴파일 시점 확인을 사용하세요

연습 문제

Notify(msg string) error를 포함하는 Notifier 인터페이스를 설계하세요. 이를 EmailNotifier와 SMSNotifier에 구현하세요. []Notifier에 전송하는 broadcast 함수를 작성하세요.

자주 묻는 질문

“인터페이스 조합과 모범 사례” 강의는 무료인가요?

네 — “인터페이스 조합과 모범 사례” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Go Academy 강의 전체를 잠금 해제할 수 있습니다. Go Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“인터페이스 조합과 모범 사례”에서 뭘 배우나요?

작은 인터페이스와 의존성 역전 브라우저에서 직접 실행하는 실습 코드로 Go Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Go Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Go Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“인터페이스 조합과 모범 사례” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Go Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Go Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 인터페이스 정의와 구현
  2. 표준 라이브러리의 핵심 인터페이스
  3. 타입 단언과 타입 스위치
  4. 인터페이스 조합과 모범 사례
← Go Academy(으)로 돌아가기