인터페이스 충족 규칙
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 107개 중 43번째.
Go는 암시적 interface 만족을 위해 덕 타이핑을 사용하지만, 타입이 실제로 interface를 만족하는지를 결정하는 구체적인 규칙이 있습니다. 이러한 규칙을 이해하면 미묘한 버그를 피하는 데 도움이 됩니다.
가장 중요한 규칙은 pointer receivers와 관련이 있습니다. 메서드가 pointer receiver로 정의된 경우, 해당 타입의 값 자체가 아니라 그 타입에 대한 포인터만 interface를 만족합니다.
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // 포인터 리시버
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
Save()에는 포인터 리시버가 있으므로 *Document만 Saver를 만족합니다:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // 작동함 - 포인터가 인터페이스를 만족함
// Process(doc) // 컴파일 오류 - 값이 인터페이스를 만족하지 않음
}
하지만 그 반대는 더 유연합니다. 메서드에 값 리시버가 있으면 값 타입과 포인터 타입 both 인터페이스를 만족합니다. Go는 값 리시버 메서드를 호출할 때 포인터를 자동으로 역참조합니다.
func (d Document) Info() string { // 값 리시버
return d.Name
}
// Document와 *Document 모두 Info()를 요구하는 인터페이스를 만족합니다
이 비대칭이 존재하는 이유는 Go가 항상 pointer에서 값(역참조를 통해)을 가져올 수 있지만, 값에서 항상 pointer를 가져올 수는 없기 때문입니다(값에 주소를 지정할 수 없을 수도 있습니다). 이 규칙을 기억해 두면 interface를 사용할 때 혼란스러운 컴파일러 오류를 방지할 수 있습니다.
챌린지
쉬움포인터 수신자와 값 수신자가 인터페이스 만족 여부에 어떤 영향을 미치는지 보여 주는 구성 시스템을 만들어 보겠습니다. 수신자 선택에 따라 값, 포인터 또는 둘 다 인터페이스와 함께 사용될 수 있는 타입을 만들게 됩니다.
코드를 세 개의 파일로 구성합니다.
config.go: 두 메서드인GetValue() string과SetValue(string)을 요구하는Configurable인터페이스를 Define합니다. 그런 다음 두 개의 구성 타입을 만듭니다.ReadOnlyConfig에는Valuefield가 있습니다.GetValue()에는 값 수신자를 사용하고(Value를 반환),SetValue()에는 포인터 수신자를 사용합니다(Value를 업데이트).Setting에는Datafield가 있습니다. 두 메서드 모두에 값 수신자를 사용합니다(GetValue는 Data를 반환하고, SetValue는 아무것도 변경하지 않고 "Cannot modify"만 출력합니다).
processor.go:Configurable과 새 값 문자열을 accepts하는ProcessConfig라는 function을 Create합니다.GetValue()를 사용해 현재 값을 출력하고, 새 값으로SetValue()를 Call한 다음, 변경 사항을 보여 주기 위해 값을 다시 출력해야 합니다.main.go: 입력에서 구성 세부 정보를 읽고 인터페이스 만족 규칙을 보여 줍니다.ReadOnlyConfig를 Create하고ProcessConfig에 포인터를 전달합니다(SetValue가 포인터 수신자를 가지므로 필요).Setting을 Create하고ProcessConfig에 값을 직접 전달합니다(두 메서드 모두 값 수신자를 가지므로 작동).
다음 입력이 제공됩니다.
- Line 1: ReadOnlyConfig의 초기 값
- Line 2: ReadOnlyConfig에 설정할 새 값
- Line 3: Setting의 초기 값
- Line 4: Setting에 설정을 시도할 새 값
ProcessConfig function은 다음 format으로 출력해야 합니다.
Current: [value]
Current: [value after SetValue]예를 들어 debug, production, localhost, remote가 주어지면 출력은 다음과 같아야 합니다.
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhostReadOnlyConfig은 포인터를 전달했기 때문에 실제로 값이 변경되는 반면, Setting은 변경되지 않는다는 점에 주목하세요(값 수신자를 사용하는 SetValue는 원본을 modify할 수 없습니다). 핵심은 ReadOnlyConfig 값만으로는 Configurable을 만족할 수 없고 포인터만 만족한다는 것입니다. 반면 Setting은 모든 메서드가 값 수신자를 사용하므로 값을 직접 사용할 수 있습니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// 입력 읽기
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: rocInitial 값으로 ReadOnlyConfig 생성
// TODO: ProcessConfig에 POINTER 전달 (SetValue가 포인터 리시버를 가지기 때문에 필요함)
// TODO: settingInitial 값으로 Setting 생성
// TODO: ProcessConfig에 VALUE를 직접 전달 (두 메서드 모두 값 리시버를 가지기 때문에 작동함)
// 사용되지 않는 변수 오류를 피하기 위해 이 변수들을 사용
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서직접 연습해 보세요: 온라인 Go 컴파일러