계약으로서의 인터페이스
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 27번째.
인터페이스를 계약으로 생각하면 더 나은 Go 프로그램을 설계하는 데 도움이 됩니다. 함수가 인터페이스 타입을 인자로 받을 때, 그것은 다음과 같이 말하는 것과 같습니다: "이 계약을 충족하기만 한다면, 어떤 구체 타입(concrete type)을 전달하든 상관없습니다."
이러한 계약 기반의 사고방식은 특정 타입보다는 동작에 집중하는 함수를 작성할 수 있게 해줍니다. 다양한 소스로부터 데이터를 처리해야 하는 함수를 생각해 봅시다:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}ProcessData 함수는 데이터베이스 연결, API 클라이언트 또는 파일 리더를 받는지 여부를 알 필요도 없고 신경 쓰지도 않습니다. 입력이 FetchData()를 수행할 수 있기만 하면 됩니다. 이 계약을 준수하는 모든 타입은 작동합니다.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // 작동합니다!
ProcessData(api) // 역시 작동합니다!
}이러한 계약 방식은 코드를 더욱 테스트 가능하게 만들어 줍니다. 테스트 중에 실제 데이터베이스나 네트워크 연결 없이도 인터페이스를 충족하는 모의 타입(mock type)을 생성할 수 있습니다. 계약이 충족되었으므로 테스트 대상 함수는 그 차이를 알지 못합니다.
챌린지
쉬움인터페이스가 어떻게 계약(contract) 역할을 하는지 보여주는 알림 시스템을 구축해 보겠습니다. 알림을 보낸다는 것이 무엇을 의미하는지 정의하는 Notifier 인터페이스를 만들고, 이 계약을 준수하는 여러 알림 채널을 구현하게 됩니다.
코드는 두 개의 파일로 구성됩니다:
notifiers.go: 단일 메서드Notify(message string) string을 가진Notifier인터페이스를 정의합니다. 그런 다음 이 계약을 충족하는 세 가지 구조체를 만듭니다:Prefix필드를 가진ConsoleNotifierFilename필드를 가진FileNotifierURL필드를 가진WebhookNotifier
Notify메서드를 구현해야 합니다.main.go:Notifier값의 슬라이스와 메시지 문자열을 인자로 받는BroadcastAlert함수를 만듭니다. 이 함수는 각 노티파이어에서Notify를 호출하고 결과를 출력해야 합니다. 입력에서 설정을 읽어 세 가지 노티파이어 타입을 모두 생성하고, 모든 노티파이어를 통해 알림을 브로드캐스트합니다.
다음 입력이 제공됩니다:
- 1행: 콘솔 접두사(Prefix)
- 2행: 파일 이름(Filename)
- 3행: 웹훅 URL
- 4행: 알림 메시지
Notify 메서드는 다음과 같은 형식의 문자열을 반환해야 합니다:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
예를 들어, ALERT:, log.txt, https://hooks.example.com, Server down!이 주어지면 출력은 다음과 같아야 합니다:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!이 설계의 장점은 BroadcastAlert 함수가 구체적인 노티파이어 타입을 알 필요도 없고 신경 쓸 필요도 없다는 것입니다. 단지 각 타입이 Notifier 계약을 충족하기만 하면 됩니다. 나중에 브로드캐스트 함수를 전혀 수정하지 않고도 SlackNotifier나 EmailNotifier를 추가할 수 있습니다.
치트 시트
Go의 인터페이스는 구체적인 타입을 지정하지 않고 필요한 동작을 정의하는 계약(contracts) 역할을 합니다. 함수가 인터페이스 타입을 매개변수로 받으면, 해당 인터페이스의 메서드를 구현하는 모든 타입을 사용할 수 있습니다.
메서드 시그니처를 지정하여 인터페이스를 정의합니다:
type DataSource interface {
FetchData() string
}인터페이스를 허용하는 함수는 계약을 충족하는 모든 타입과 함께 작동합니다:
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}여러 타입이 동일한 인터페이스를 구현할 수 있습니다:
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // 작동합니다!
ProcessData(api) // 역시 작동합니다!
}이러한 계약 기반 접근 방식은 다음을 가능하게 합니다:
- 특정 타입보다는 동작에 집중하는 함수
- 모의(mock) 구현을 통한 더 나은 테스트 가능성
- 기존 코드를 수정하지 않고도 확장 가능
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert는 모든 알림 도구(notifiers)를 통해 메시지를 전송합니다
// TODO: 다음 기능을 수행하도록 이 함수를 구현하세요:
// - Notifier 값의 슬라이스와 메시지 문자열을 인자로 받습니다
// - 각 알림 도구에서 Notify를 호출하고 결과를 출력합니다
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: 알림 도구들을 순회하며 각 알림 결과를 출력하세요
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 콘솔 접두사(prefix)를 읽습니다
scanner.Scan()
prefix := scanner.Text()
// 파일 이름을 읽습니다
scanner.Scan()
filename := scanner.Text()
// 웹훅 URL을 읽습니다
scanner.Scan()
url := scanner.Text()
// 알림 메시지를 읽습니다
scanner.Scan()
message := scanner.Text()
// TODO: 세 가지 알림 도구 타입의 인스턴스를 모두 생성하세요
// - prefix를 사용한 ConsoleNotifier
// - filename을 사용한 FileNotifier
// - URL을 사용한 WebhookNotifier
// TODO: 세 가지 알림 도구를 모두 포함하는 Notifier 슬라이스를 생성하세요
// TODO: 알림 도구들과 메시지를 사용하여 BroadcastAlert를 호출하세요
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서