backend

Go 언어 인터페이스(interface): 느슨한 결합과 다형성 이해하기

mirabo01 2026. 1. 13. 21:55

구조체와 메서드까지 익혔다면, 이제 Go 설계의 핵심이라고 할 수 있는
interface를 이해할 차례다.

Go 인터페이스는 다른 언어의 인터페이스나 추상 클래스와 개념적으로 비슷해 보이지만,
사용 방식과 철학은 꽤 다르다.
특히 “명시적으로 구현하지 않는다”는 점에서 처음 접하면 헷갈리기 쉽다.

이 글에서는

  • Go 인터페이스가 무엇인지
  • 어떻게 구현되는지
  • 실무에서 왜 자주 쓰이는지

를 예제와 함께 정리해본다.


interface란 무엇인가

Go에서 인터페이스는 메서드 집합의 정의다.

type Reader interface {
    Read() string
}
  • 필드는 없다
  • 어떤 메서드를 가져야 하는지만 정의한다

이 인터페이스를 구현하려면,
해당 메서드를 가진 타입이면 자동으로 인터페이스를 만족한다.


명시적인 implements가 없다

Go 인터페이스의 가장 큰 특징은
**“implements 키워드가 없다”**는 점이다.

type File struct {}

func (f File) Read() string {
    return "file read"
}

이 상태에서 File은 Reader 인터페이스를 이미 구현한 상태다.
어디에도 “Reader를 구현한다”는 선언은 없다.

실제로 써보면, 이 방식이 코드 결합도를 크게 낮춰준다.
타입을 작성할 때 인터페이스를 의식하지 않아도 되기 때문이다.

[이미지: Go interface 암묵적 구현 구조]


인터페이스 사용 예

func ReadData(r Reader) {
    fmt.Println(r.Read())
}
f := File{}
ReadData(f)
  • 함수는 구체 타입이 아닌 인터페이스에 의존
  • 실제 동작은 전달된 타입이 결정

이 구조 덕분에,
코드를 수정하지 않고도 새로운 타입을 쉽게 끼워 넣을 수 있다.


작은 인터페이스가 권장된다

Go에서는 다음과 같은 원칙이 자주 언급된다.

인터페이스는 작을수록 좋다.

표준 라이브러리에서도 이런 패턴을 쉽게 볼 수 있다.

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

메서드 하나짜리 인터페이스도 전혀 이상하지 않다.
오히려 이런 인터페이스가 재사용성이 높다.

실제로 써보면,
“미리 인터페이스를 설계한다”기보다
필요해졌을 때 인터페이스를 뽑아내는 방식이 더 자연스럽다.


빈 인터페이스(interface{})

var v interface{}
  • 어떤 타입이든 담을 수 있다
  • 타입 안정성은 컴파일 타임에 보장되지 않는다
v = 10
v = "hello"

이 방식은 편리해 보이지만, 남용하면 코드가 읽기 어려워진다.

⚠️ 주의할 점
빈 인터페이스를 사용하면 결국 타입 단언(type assertion)이 필요해진다.

s, ok := v.(string)

실무에서는

  • 정말 범용 컨테이너가 필요할 때
  • 표준 라이브러리와 맞닿는 지점

에서만 제한적으로 사용하는 편이 낫다.


interface를 통한 설계의 장점

Go의 인터페이스는
구현보다 행위에 집중하게 만든다

  • 테스트 코드 작성이 쉬워진다
  • 의존성이 줄어든다
  • 코드 변경 범위가 작아진다

특히 mock 객체를 만들 때,
인터페이스 기반 설계의 장점이 분명하게 드러난다.


언제 인터페이스를 쓰는 게 좋을까

  • 여러 구현체가 존재할 가능성이 있는 경우
  • 외부 의존성을 분리하고 싶은 경우
  • 테스트 대역(mock, stub)이 필요한 경우

반대로,
구현체가 하나뿐이고 변경 가능성도 낮다면
굳이 인터페이스를 먼저 만들 필요는 없다.


정리

Go 인터페이스는
“미리 설계하는 도구”라기보다
필요해질 때 꺼내 쓰는 추상화 수단에 가깝다.

  • 명시적 구현 선언이 없다
  • 작은 인터페이스가 권장된다
  • 코드 결합도를 낮추는 데 효과적이다

다음 글에서는 이 인터페이스를 실제로 활용하는 예로
에러 처리 패턴과 error 인터페이스,
그리고 Go에서의 에러 설계 방식으로 이어가면 흐름이 좋다.