15 minute read

개요

인터페이스는 Go의 타입 시스템의 핵심으로, 메서드 시그니처의 집합을 정의합니다.

주요 특징:

  • 메서드 집합: 타입이 구현해야 할 메서드들을 정의
  • 암시적 구현: 명시적 선언 없이 자동으로 구현됨 (덕 타이핑)
  • 다형성: 서로 다른 타입을 하나의 인터페이스로 처리
  • 작은 인터페이스: 1-3개 메서드 권장 (단일 책임 원칙)
  • 조합 가능: 인터페이스 임베딩으로 조합
  • 타입 안전성: 컴파일 타임에 검증
  • nil 가능: 인터페이스 변수는 nil일 수 있음

인터페이스 정의 및 구현

1. 기본 인터페이스

package main

import "fmt"

// 인터페이스 정의
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle 구조체
type Rectangle struct {
    Width  float64
    Height float64
}

// Rectangle이 Shape 인터페이스를 구현
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// Circle 구조체
type Circle struct {
    Radius float64
}

// Circle도 Shape 인터페이스를 구현
func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * 3.14159 * c.Radius
}

func printShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    circle := Circle{Radius: 7}
    
    printShapeInfo(rect)   // Area: 50.00, Perimeter: 30.00
    printShapeInfo(circle) // Area: 153.94, Perimeter: 43.98
}

2. 암시적 구현 (Duck Typing)

// 인터페이스 정의
type Writer interface {
    Write([]byte) (int, error)
}

// 명시적 선언 없이 구현
type FileWriter struct {
    filename string
}

func (fw FileWriter) Write(data []byte) (int, error) {
    // FileWriter는 자동으로 Writer 인터페이스를 구현함
    fmt.Printf("Writing %d bytes to %s\n", len(data), fw.filename)
    return len(data), nil
}

func saveData(w Writer, data []byte) {
    w.Write(data)
}

func main() {
    fw := FileWriter{filename: "output.txt"}
    saveData(fw, []byte("Hello, World!"))
}

3. 포인터 vs 값 리시버

type Counter interface {
    Increment()
    Value() int
}

// ❌ 값 리시버 - 원본이 수정되지 않음
type BadCounter struct {
    count int
}

func (c BadCounter) Increment() {
    c.count++ // 복사본만 수정됨
}

func (c BadCounter) Value() int {
    return c.count
}

// ✅ 포인터 리시버 - 원본이 수정됨
type GoodCounter struct {
    count int
}

func (c *GoodCounter) Increment() {
    c.count++ // 원본 수정됨
}

func (c *GoodCounter) Value() int {
    return c.count
}

func main() {
    // BadCounter
    bc := BadCounter{}
    bc.Increment()
    fmt.Println(bc.Value()) // 0 (변경 안됨!)
    
    // GoodCounter
    gc := &GoodCounter{}
    gc.Increment()
    fmt.Println(gc.Value()) // 1 (변경됨)
}

빈 인터페이스 (Empty Interface)

1. interface{} (Go 1.17 이하)

func printAnything(v interface{}) {
    fmt.Println(v)
}

func main() {
    printAnything(42)
    printAnything("hello")
    printAnything([]int{1, 2, 3})
    printAnything(struct{ Name string }{"Alice"})
}

2. any (Go 1.18+)

// any는 interface{}의 별칭
func printAnything(v any) {
    fmt.Println(v)
}

// 여러 타입을 담는 슬라이스
func mixedSlice() {
    items := []any{
        42,
        "hello",
        true,
        3.14,
        []int{1, 2, 3},
    }
    
    for _, item := range items {
        fmt.Printf("%T: %v\n", item, item)
    }
}

타입 단언 (Type Assertion)

1. 기본 타입 단언

func typeAssertion() {
    var i interface{} = "hello"
    
    // 안전하지 않은 단언 (실패 시 패닉)
    s := i.(string)
    fmt.Println(s) // "hello"
    
    // 안전한 단언 (Comma Ok Idiom)
    s2, ok := i.(string)
    if ok {
        fmt.Println("String:", s2)
    }
    
    // 타입이 맞지 않으면
    n, ok := i.(int)
    if !ok {
        fmt.Println("Not an int, got:", n) // 0
    }
    
    // ❌ 패닉 발생!
    // n := i.(int) // panic: interface conversion: interface {} is string, not int
}

2. 실전 활용

func processValue(v interface{}) {
    // 타입에 따라 다르게 처리
    if str, ok := v.(string); ok {
        fmt.Println("String length:", len(str))
        return
    }
    
    if num, ok := v.(int); ok {
        fmt.Println("Number doubled:", num*2)
        return
    }
    
    if slice, ok := v.([]int); ok {
        fmt.Println("Slice sum:", sum(slice))
        return
    }
    
    fmt.Println("Unknown type")
}

func sum(nums []int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

타입 스위치 (Type Switch)

func typeSwitch(v interface{}) {
    switch val := v.(type) {
    case string:
        fmt.Printf("String of length %d: %s\n", len(val), val)
    case int:
        fmt.Printf("Integer: %d\n", val)
    case bool:
        fmt.Printf("Boolean: %t\n", val)
    case []int:
        fmt.Printf("Int slice of length %d\n", len(val))
    case nil:
        fmt.Println("nil value")
    default:
        fmt.Printf("Unknown type: %T\n", val)
    }
}

func main() {
    typeSwitch("hello")      // String of length 5: hello
    typeSwitch(42)           // Integer: 42
    typeSwitch(true)         // Boolean: true
    typeSwitch([]int{1, 2})  // Int slice of length 2
    typeSwitch(nil)          // nil value
    typeSwitch(3.14)         // Unknown type: float64
}

다중 타입 처리

func handleValue(v interface{}) {
    switch v.(type) {
    case int, int64, int32:
        fmt.Println("Some kind of int")
    case string:
        fmt.Println("String type")
    case []byte, []rune:
        fmt.Println("Byte or rune slice")
    }
}

인터페이스 조합 (Interface Embedding)

// 작은 인터페이스들
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// 인터페이스 조합
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// 구현 예제
type File struct {
    name string
}

func (f *File) Read(p []byte) (int, error) {
    fmt.Printf("Reading from %s\n", f.name)
    return len(p), nil
}

func (f *File) Write(p []byte) (int, error) {
    fmt.Printf("Writing to %s\n", f.name)
    return len(p), nil
}

func (f *File) Close() error {
    fmt.Printf("Closing %s\n", f.name)
    return nil
}

func main() {
    file := &File{name: "data.txt"}
    
    // File은 ReadWriteCloser 인터페이스를 구현
    var rwc ReadWriteCloser = file
    
    rwc.Write([]byte("hello"))
    rwc.Read(make([]byte, 10))
    rwc.Close()
}

표준 라이브러리 인터페이스

1. error 인터페이스

type error interface {
    Error() string
}

// 커스텀 에러 타입
type ValidationError struct {
    Field string
    Issue string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Issue)
}

func validateAge(age int) error {
    if age < 0 {
        return ValidationError{
            Field: "age",
            Issue: "cannot be negative",
        }
    }
    if age > 150 {
        return ValidationError{
            Field: "age",
            Issue: "unrealistic value",
        }
    }
    return nil
}

func main() {
    if err := validateAge(-5); err != nil {
        fmt.Println(err) // validation error on age: cannot be negative
        
        // 타입 단언으로 상세 정보 접근
        if ve, ok := err.(ValidationError); ok {
            fmt.Printf("Field: %s, Issue: %s\n", ve.Field, ve.Issue)
        }
    }
}

2. fmt.Stringer 인터페이스

type Stringer interface {
    String() string
}

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    fmt.Println(p) // Alice (30 years old)
    
    // Stringer가 없으면: {Alice 30}
}

3. io.Reader와 io.Writer

import (
    "io"
    "strings"
    "os"
)

func copyData(src io.Reader, dst io.Writer) error {
    _, err := io.Copy(dst, src)
    return err
}

func main() {
    // strings.Reader는 io.Reader 구현
    reader := strings.NewReader("Hello, World!")
    
    // os.Stdout는 io.Writer 구현
    copyData(reader, os.Stdout) // Hello, World!
}

4. sort.Interface

import "sort"

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

type Person struct {
    Name string
    Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    people := []Person{
        {"Bob", 31},
        {"Alice", 23},
        {"Charlie", 25},
    }
    
    sort.Sort(ByAge(people))
    fmt.Println(people)
    // [{Alice 23} {Charlie 25} {Bob 31}]
}

인터페이스 내부 구조

1. 개념적 구조

// 인터페이스는 두 가지 구조로 구현됨

// 1. iface (메서드가 있는 인터페이스)
type iface struct {
    tab  *itab        // 타입 정보와 메서드 테이블
    data unsafe.Pointer // 실제 값의 포인터
}

// 2. eface (빈 인터페이스 interface{}/any)
type eface struct {
    _type *_type       // 타입 정보
    data  unsafe.Pointer // 실제 값의 포인터
}

2. nil 인터페이스 vs nil 값을 가진 인터페이스

func nilInterface() {
    var i interface{}
    fmt.Println(i == nil) // true (타입과 값 모두 nil)
    
    var p *int
    i = p
    fmt.Println(i == nil) // false! (타입은 *int, 값은 nil)
    
    // 올바른 검사
    if i == nil {
        fmt.Println("Interface is nil")
    } else {
        // 값이 nil인지 확인하려면 리플렉션 필요
        if p, ok := i.(*int); ok && p == nil {
            fmt.Println("Value is nil")
        }
    }
}

3. 인터페이스 비교

func interfaceComparison() {
    var i1 interface{} = 42
    var i2 interface{} = 42
    
    fmt.Println(i1 == i2) // true
    
    i1 = []int{1, 2, 3}
    i2 = []int{1, 2, 3}
    
    // ❌ 패닉! 슬라이스는 비교 불가능
    // fmt.Println(i1 == i2) // panic: runtime error
}

실전 활용 패턴

1. 의존성 주입 (Dependency Injection)

// 인터페이스로 의존성 정의
type Database interface {
    Save(key string, value interface{}) error
    Load(key string) (interface{}, error)
}

// 실제 구현
type PostgresDB struct {
    connString string
}

func (db *PostgresDB) Save(key string, value interface{}) error {
    fmt.Printf("Saving to Postgres: %s = %v\n", key, value)
    return nil
}

func (db *PostgresDB) Load(key string) (interface{}, error) {
    fmt.Printf("Loading from Postgres: %s\n", key)
    return "some value", nil
}

// 서비스는 인터페이스에 의존
type UserService struct {
    db Database
}

func (s *UserService) SaveUser(name string) error {
    return s.db.Save("user:"+name, name)
}

func main() {
    db := &PostgresDB{connString: "postgres://..."}
    service := &UserService{db: db}
    service.SaveUser("Alice")
}

2. 모킹 (Mocking)

// 테스트용 Mock 구현
type MockDB struct {
    data map[string]interface{}
}

func NewMockDB() *MockDB {
    return &MockDB{
        data: make(map[string]interface{}),
    }
}

func (m *MockDB) Save(key string, value interface{}) error {
    m.data[key] = value
    return nil
}

func (m *MockDB) Load(key string) (interface{}, error) {
    if val, ok := m.data[key]; ok {
        return val, nil
    }
    return nil, fmt.Errorf("key not found")
}

func TestUserService() {
    // 실제 DB 대신 Mock 사용
    mockDB := NewMockDB()
    service := &UserService{db: mockDB}
    
    service.SaveUser("Bob")
    
    if val, err := mockDB.Load("user:Bob"); err == nil {
        fmt.Println("Found user:", val)
    }
}

3. 전략 패턴 (Strategy Pattern)

type CompressionStrategy interface {
    Compress(data []byte) []byte
}

type ZipCompression struct{}

func (z ZipCompression) Compress(data []byte) []byte {
    fmt.Println("Compressing with ZIP")
    return data // 실제로는 압축 로직
}

type GzipCompression struct{}

func (g GzipCompression) Compress(data []byte) []byte {
    fmt.Println("Compressing with GZIP")
    return data // 실제로는 압축 로직
}

type Compressor struct {
    strategy CompressionStrategy
}

func (c *Compressor) SetStrategy(s CompressionStrategy) {
    c.strategy = s
}

func (c *Compressor) Compress(data []byte) []byte {
    return c.strategy.Compress(data)
}

func main() {
    comp := &Compressor{}
    data := []byte("some data")
    
    comp.SetStrategy(ZipCompression{})
    comp.Compress(data)
    
    comp.SetStrategy(GzipCompression{})
    comp.Compress(data)
}

4. 플러그인 아키텍처

type Plugin interface {
    Name() string
    Execute(args map[string]interface{}) error
}

type PluginRegistry struct {
    plugins map[string]Plugin
}

func NewPluginRegistry() *PluginRegistry {
    return &PluginRegistry{
        plugins: make(map[string]Plugin),
    }
}

func (r *PluginRegistry) Register(p Plugin) {
    r.plugins[p.Name()] = p
}

func (r *PluginRegistry) Execute(name string, args map[string]interface{}) error {
    if plugin, ok := r.plugins[name]; ok {
        return plugin.Execute(args)
    }
    return fmt.Errorf("plugin not found: %s", name)
}

// 플러그인 구현 예제
type EmailPlugin struct{}

func (e EmailPlugin) Name() string {
    return "email"
}

func (e EmailPlugin) Execute(args map[string]interface{}) error {
    fmt.Printf("Sending email with args: %v\n", args)
    return nil
}

type SMSPlugin struct{}

func (s SMSPlugin) Name() string {
    return "sms"
}

func (s SMSPlugin) Execute(args map[string]interface{}) error {
    fmt.Printf("Sending SMS with args: %v\n", args)
    return nil
}

func main() {
    registry := NewPluginRegistry()
    registry.Register(EmailPlugin{})
    registry.Register(SMSPlugin{})
    
    registry.Execute("email", map[string]interface{}{"to": "user@example.com"})
    registry.Execute("sms", map[string]interface{}{"to": "+1234567890"})
}

5. 어댑터 패턴 (Adapter Pattern)

// 레거시 타입
type LegacyPrinter struct{}

func (lp *LegacyPrinter) PrintOldWay(text string) {
    fmt.Println("OLD:", text)
}

// 새로운 인터페이스
type ModernPrinter interface {
    Print(text string)
}

// 어댑터
type PrinterAdapter struct {
    legacy *LegacyPrinter
}

func (pa *PrinterAdapter) Print(text string) {
    pa.legacy.PrintOldWay(text)
}

func usePrinter(p ModernPrinter, text string) {
    p.Print(text)
}

func main() {
    legacy := &LegacyPrinter{}
    adapter := &PrinterAdapter{legacy: legacy}
    
    usePrinter(adapter, "Hello, World!")
}

6. 옵저버 패턴 (Observer Pattern)

type Observer interface {
    Update(message string)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) Attach(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(message string) {
    for _, observer := range s.observers {
        observer.Update(message)
    }
}

type EmailObserver struct {
    email string
}

func (e *EmailObserver) Update(message string) {
    fmt.Printf("Email to %s: %s\n", e.email, message)
}

type SMSObserver struct {
    phone string
}

func (s *SMSObserver) Update(message string) {
    fmt.Printf("SMS to %s: %s\n", s.phone, message)
}

func main() {
    subject := &Subject{}
    
    subject.Attach(&EmailObserver{email: "user@example.com"})
    subject.Attach(&SMSObserver{phone: "+1234567890"})
    
    subject.Notify("System update available!")
}

제네릭과 인터페이스 (Go 1.18+)

1. 제네릭 인터페이스

type Comparable[T any] interface {
    CompareTo(other T) int
}

type Number struct {
    value int
}

func (n Number) CompareTo(other Number) int {
    return n.value - other.value
}

func Max[T Comparable[T]](a, b T) T {
    if a.CompareTo(b) > 0 {
        return a
    }
    return b
}

2. 타입 제약 (Type Constraints)

type Ordered interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64 | ~string
}

func Min[T Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Min(10, 20))        // 10
    fmt.Println(Min(3.14, 2.71))    // 2.71
    fmt.Println(Min("apple", "banana")) // apple
}

3. comparable 제약

func Contains[T comparable](slice []T, item T) bool {
    for _, v := range slice {
        if v == item {
            return true
        }
    }
    return false
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    fmt.Println(Contains(nums, 3)) // true
    
    strs := []string{"a", "b", "c"}
    fmt.Println(Contains(strs, "d")) // false
}

성능 고려사항

1. 인터페이스 호출 오버헤드

import "testing"

type Adder interface {
    Add(a, b int) int
}

type Calculator struct{}

func (c Calculator) Add(a, b int) int {
    return a + b
}

func directCall(c Calculator) int {
    return c.Add(1, 2)
}

func interfaceCall(a Adder) int {
    return a.Add(1, 2)
}

func BenchmarkDirectCall(b *testing.B) {
    c := Calculator{}
    for i := 0; i < b.N; i++ {
        directCall(c)
    }
}

func BenchmarkInterfaceCall(b *testing.B) {
    var a Adder = Calculator{}
    for i := 0; i < b.N; i++ {
        interfaceCall(a)
    }
}

// 인터페이스 호출은 직접 호출보다 약간 느림 (나노초 단위)
// 대부분의 경우 무시할 수 있는 수준

2. 빈 인터페이스 vs 제네릭

// ❌ 빈 인터페이스 - 런타임 타입 단언 필요
func SumAny(items []interface{}) int {
    sum := 0
    for _, item := range items {
        if num, ok := item.(int); ok {
            sum += num
        }
    }
    return sum
}

// ✅ 제네릭 - 컴파일 타임에 타입 안전
func Sum[T ~int | ~int64](items []T) T {
    var sum T
    for _, item := range items {
        sum += item
    }
    return sum
}

// 제네릭이 더 안전하고 빠름

3. 인터페이스 값 vs 포인터

type Data struct {
    value [1024]byte // 큰 구조체
}

type Processor interface {
    Process()
}

// ❌ 값 리시버 - 구조체 복사 발생
func (d Data) Process() {
    // 1024 바이트 복사
}

// ✅ 포인터 리시버 - 포인터만 복사
func (d *Data) Process() {
    // 8 바이트 포인터만 복사
}

// 큰 구조체는 포인터 리시버 사용 권장

일반적인 실수

1. nil 인터페이스 혼동

func mistake1() {
    var p *int = nil
    var i interface{} = p
    
    // ❌ false! (타입 정보가 있음)
    fmt.Println(i == nil) // false
    
    // ✅ 올바른 검사
    if i == nil {
        fmt.Println("Interface is nil")
    } else {
        fmt.Println("Interface is not nil, but value might be")
    }
}

func returnNilInterface() interface{} {
    var p *int = nil
    return p // ❌ nil이 아닌 인터페이스 반환!
}

func main() {
    result := returnNilInterface()
    if result == nil {
        fmt.Println("nil") // 출력되지 않음!
    } else {
        fmt.Println("not nil") // 출력됨
    }
}

2. 타입 단언 없이 panic

func mistake2() {
    var i interface{} = "hello"
    
    // ❌ 패닉 발생!
    // num := i.(int) // panic
    
    // ✅ 안전한 방법
    num, ok := i.(int)
    if !ok {
        fmt.Println("Not an int")
    } else {
        fmt.Println(num)
    }
}

3. 빈 인터페이스 남용

// ❌ 타입 안전성 상실
func badFunction(data interface{}) {
    // 런타임에 타입 검사 필요
    if str, ok := data.(string); ok {
        // ...
    }
}

// ✅ 구체적인 타입 또는 제네릭 사용
func goodFunction(data string) {
    // 컴파일 타임에 타입 안전
}

func genericFunction[T any](data T) {
    // 제네릭으로 타입 안전성 유지
}

4. 인터페이스를 반환할 때 nil 처리

type MyError struct {
    msg string
}

func (e *MyError) Error() string {
    return e.msg
}

// ❌ 잘못된 패턴
func badErrorHandling() error {
    var err *MyError = nil
    if someCondition := false; someCondition {
        err = &MyError{msg: "error"}
    }
    return err // nil *MyError를 반환하지만 error 인터페이스는 nil이 아님!
}

// ✅ 올바른 패턴
func goodErrorHandling() error {
    if someCondition := false; someCondition {
        return &MyError{msg: "error"}
    }
    return nil // nil 인터페이스 반환
}

func main() {
    if err := badErrorHandling(); err != nil {
        fmt.Println("Error occurred") // 항상 실행됨!
    }
    
    if err := goodErrorHandling(); err != nil {
        fmt.Println("Error occurred") // 조건에 따라 실행
    }
}

5. 큰 인터페이스 정의

// ❌ 너무 큰 인터페이스
type BadRepository interface {
    Create(entity interface{}) error
    Read(id int) (interface{}, error)
    Update(entity interface{}) error
    Delete(id int) error
    List() ([]interface{}, error)
    Search(query string) ([]interface{}, error)
    Count() int
    // ... 더 많은 메서드
}

// ✅ 작은 인터페이스로 분리
type Creator interface {
    Create(entity interface{}) error
}

type Reader interface {
    Read(id int) (interface{}, error)
}

type Updater interface {
    Update(entity interface{}) error
}

type Deleter interface {
    Delete(id int) error
}

// 필요한 것만 조합
type Repository interface {
    Creator
    Reader
    Updater
    Deleter
}

6. 포인터 리시버와 값 리시버 혼용

type Counter struct {
    count int
}

// 값 리시버
func (c Counter) Get() int {
    return c.count
}

// 포인터 리시버
func (c *Counter) Increment() {
    c.count++
}

type Getter interface {
    Get() int
}

type Incrementer interface {
    Increment()
}

func mistake6() {
    c := Counter{}
    
    // ✅ OK
    var g Getter = c
    _ = g
    
    // ❌ 컴파일 에러: Counter는 Incrementer 구현 안 함
    // var inc Incrementer = c
    
    // ✅ OK: 포인터는 가능
    var inc Incrementer = &c
    _ = inc
}

인터페이스 설계 원칙

1. 작은 인터페이스

// ✅ 단일 메서드 인터페이스
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// 필요시 조합
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

2. Accept Interfaces, Return Structs

// ✅ 함수는 인터페이스를 받음
func ProcessData(r io.Reader) error {
    // ...
    return nil
}

// ✅ 함수는 구체 타입을 반환
func NewReader(filename string) (*FileReader, error) {
    // ...
    return &FileReader{}, nil
}

// ❌ 인터페이스 반환은 피하기 (필요한 경우 제외)
func NewReaderBad(filename string) (io.Reader, error) {
    // 테스트 어려움, 타입 단언 필요
    return &FileReader{}, nil
}

3. 클라이언트가 인터페이스를 정의

// 라이브러리 코드 (구체 타입 제공)
type HTTPClient struct {
    // ...
}

func (c *HTTPClient) Get(url string) (*Response, error) {
    // ...
    return nil, nil
}

func (c *HTTPClient) Post(url string, body []byte) (*Response, error) {
    // ...
    return nil, nil
}

// 클라이언트 코드 (필요한 인터페이스만 정의)
type MyHTTPClient interface {
    Get(url string) (*Response, error)
    // Post는 사용하지 않으므로 포함 안 함
}

func fetchData(client MyHTTPClient) {
    client.Get("https://example.com")
}

정리

  • 인터페이스는 메서드 시그니처의 집합
  • 암시적 구현: implements 키워드 없음
  • 작은 인터페이스 권장 (1-3개 메서드)
  • 타입 단언: value, ok := i.(Type) 패턴 사용
  • 타입 스위치: switch v.(type) 으로 타입별 처리
  • 인터페이스 조합: 임베딩으로 작은 인터페이스 조합
  • 표준 인터페이스: error, Stringer, Reader, Writer 등
  • nil 인터페이스: 타입과 값이 모두 nil이어야 == nil
  • 의존성 주입: 인터페이스로 결합도 낮춤
  • Accept interfaces, return structs 원칙
  • 큰 구조체는 포인터 리시버 사용
  • 빈 인터페이스 남용 피하기 (제네릭 고려)
  • 클라이언트가 인터페이스를 정의하는 것이 유연함