Menu
Coddy logo textTech

인터페이스 충족 규칙

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()에는 포인터 리시버가 있으므로 *DocumentSaver를 만족합니다:

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를 사용할 때 혼란스러운 컴파일러 오류를 방지할 수 있습니다.

challenge icon

챌린지

쉬움

포인터 수신자와 값 수신자가 인터페이스 만족 여부에 어떤 영향을 미치는지 보여 주는 구성 시스템을 만들어 보겠습니다. 수신자 선택에 따라 값, 포인터 또는 둘 다 인터페이스와 함께 사용될 수 있는 타입을 만들게 됩니다.

코드를 세 개의 파일로 구성합니다.

  • config.go: 두 메서드인 GetValue() stringSetValue(string)을 요구하는 Configurable 인터페이스를 Define합니다. 그런 다음 두 개의 구성 타입을 만듭니다.
    • ReadOnlyConfig에는 Value field가 있습니다. GetValue()에는 값 수신자를 사용하고(Value를 반환), SetValue()에는 포인터 수신자를 사용합니다(Value를 업데이트).
    • Setting에는 Data field가 있습니다. 두 메서드 모두에 값 수신자를 사용합니다(GetValue는 Data를 반환하고, SetValue는 아무것도 변경하지 않고 "Cannot modify"만 출력합니다).
  • processor.go: Configurable과 새 값 문자열을 accepts하는 ProcessConfig라는 function을 Create합니다. GetValue()를 사용해 현재 값을 출력하고, 새 값으로 SetValue()를 Call한 다음, 변경 사항을 보여 주기 위해 값을 다시 출력해야 합니다.
  • main.go: 입력에서 구성 세부 정보를 읽고 인터페이스 만족 규칙을 보여 줍니다.
    1. ReadOnlyConfig를 Create하고 ProcessConfig포인터를 전달합니다(SetValue가 포인터 수신자를 가지므로 필요).
    2. 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: localhost

ReadOnlyConfig은 포인터를 전달했기 때문에 실제로 값이 변경되는 반면, 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
}
quiz icon실력 점검

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

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

직접 연습해 보세요: 온라인 Go 컴파일러