Menu
Coddy logo textTech

암시적 구현

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 26번째.

Java나 C#과 같은 많은 객체 지향 언어에서는 implements와 같은 키워드를 사용하여 타입이 인터페이스를 구현한다는 것을 명시적으로 선언해야 합니다. Go는 다른 방식을 취합니다. 인터페이스 구현이 암시적입니다.

타입은 단순히 모든 메서드를 구현함으로써 인터페이스를 충족합니다. 어디에서도 그 관계를 선언할 필요가 없습니다. 컴파일러가 자동으로 이를 파악합니다.

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriter는 암시적으로 Writer를 구현합니다
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

참고로 FileWriter에는 implements Writer 선언이 없습니다. FileWriter가 인터페이스 시그니처와 일치하는 Write(data string) int 메서드를 가지고 있기 때문에, 자동으로 Writer 인터페이스를 충족합니다.

이러한 설계는 강력한 결과를 가져옵니다: 타입이 이미 존재한 후에 인터페이스를 정의할 수 있습니다. 만약 서드파티 라이브러리에 필요한 메서드를 가진 타입이 있다면, 원본 코드를 수정하지 않고도 해당 타입이 이미 충족하는 인터페이스를 생성할 수 있습니다.

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // FileWriter가 Writer를 만족하므로 동작합니다
}

암시적 구현은 Go 코드를 결합도가 낮고 유연하게 유지해 줍니다. 타입은 인터페이스에 대해 미리 알 필요가 없으므로, 독립적인 부품들로부터 시스템을 구성하기가 더 쉬워집니다.

challenge icon

챌린지

쉬움

Go에서 타입이 인터페이스를 암시적으로 충족하는 방법을 보여주는 메시지 전달 시스템을 구축해 보겠습니다. 명시적으로 관계를 선언하지 않고도 동일한 인터페이스를 충족하는 서로 다른 메신저 타입을 만들게 됩니다.

코드는 두 개의 파일로 구성됩니다:

  • messengers.go: 단일 메서드 Send(message string) string을 가진 Messenger 인터페이스를 정의합니다. 그런 다음 이 인터페이스를 암시적으로 충족할 두 개의 구조체를 만듭니다:
    • Address 필드(string)를 가진 EmailMessenger
    • PhoneNumber 필드(string)를 가진 SMSMessenger
    각 구조체는 전달 내용을 설명하는 문자열을 반환하는 Send 메서드를 가져야 합니다. 어떤 구조체도 Messenger를 구현한다고 명시적으로 선언해서는 안 됩니다. Go는 메서드 시그니처를 기반으로 이를 자동으로 파악합니다.
  • main.go: 임의의 Messenger와 메시지 문자열을 인자로 받아 Send를 호출한 결과를 반환하는 DeliverMessage라는 함수를 만듭니다. 입력에서 연락처 정보를 읽고, 두 메신저 타입을 모두 생성한 다음, 함수를 사용하여 각각을 통해 메시지를 전달합니다.

다음 입력이 제공됩니다:

  • 1행: 이메일 주소
  • 2행: 전화번호
  • 3행: 보낼 메시지

Send 메서드는 다음 형식의 문자열을 반환해야 합니다:

  • EmailMessenger: Email to [Address]: [message]
  • SMSMessenger: SMS to [PhoneNumber]: [message]

예를 들어, alice@example.com, 555-1234, Hello!가 주어지면 출력은 다음과 같아야 합니다:

Email to alice@example.com: Hello!
SMS to 555-1234: Hello!

여기서 핵심은 DeliverMessage 함수가 모든 Messenger를 수용하며, EmailMessengerSMSMessenger 모두 일치하는 Send 메서드를 가짐으로써 해당 인터페이스를 충족한다는 점입니다. implements 키워드는 어디에도 없으며, Go의 암시적 구현(implicit implementation)이 모든 것을 처리합니다.

치트 시트

Go에서 인터페이스 구현은 암시적(implicit)입니다. 타입은 단순히 인터페이스의 모든 메서드를 구현함으로써 해당 인터페이스를 충족하며, 별도의 명시적인 선언은 필요하지 않습니다.

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriter는 암시적으로 Writer를 구현합니다.
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

implements Writer와 같은 선언이 없습니다. FileWriter가 인터페이스 시그니처와 일치하는 Write(data string) int 메서드를 가지고 있기 때문에, 자동으로 Writer 인터페이스를 충족하게 됩니다.

타입이 인터페이스에 대해 알지 못하더라도, 해당 인터페이스를 충족하는 모든 타입을 사용할 수 있습니다:

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // FileWriter가 Writer를 충족하므로 작동합니다.
}

이러한 설계 덕분에 타입이 이미 존재한 후에 인터페이스를 정의할 수 있으며, 코드를 분리되고 유연하게 만들 수 있습니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
)

// TODO: 임의의 Messenger와 메시지 문자열을 받아 Send를 호출한 결과를 
// 반환하는 DeliverMessage 함수를 만드세요.

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// 이메일 주소 읽기
	email, _ := reader.ReadString('\n')
	email = email[:len(email)-1]
	
	// 전화번호 읽기
	phone, _ := reader.ReadString('\n')
	phone = phone[:len(phone)-1]
	
	// 메시지 읽기
	message, _ := reader.ReadString('\n')
	if len(message) > 0 && message[len(message)-1] == '\n' {
		message = message[:len(message)-1]
	}
	
	// TODO: 이메일 주소를 사용하여 EmailMessenger를 생성하세요.
	
	// TODO: 전화번호를 사용하여 SMSMessenger를 생성하세요.
	
	// TODO: DeliverMessage를 사용하여 각 메신저를 통해 메시지를 전송하고
	// 그 결과를 출력하세요.
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨