본문으로 바로가기

인터페이스?

 

inteface는 polymorphism(다형성)을 구현할 수 있는 좋은 타입이다.

interface도 타입이다! lang spec을 찾아보라. 그리고 애초에 선언이 type xxx interface 꼴이다.

interface는 타입(type)이 구현해야 하는 메서드 원형(prototype)들을 정의한다. 아래 처럼.

// Area 메서드를 가지는 것은 Figure 타입이다. 이렇게 이해하는 게 편하다 (Todd McLeod)
type Figure interface {
   Area() float32
}

 

잘 생각해보면, typescript에서의 interface도 같았다. 타입처럼 지정하고, 특정 속성(그 속성이 함수일 수도 있다)을 가져야만 interface를 만족했었다. 이런 관점에서 보면 go의 interface나 ts에서의 interface는 같은 것이다.

https://darrengwon.tistory.com/117?category=867626 

 

interface 기초와 활용 (Read-only properties, Indexable Types, extends)

interface 기본 (https://typescript-kr.github.io/pages/interfaces.html) (typescript-handbook-ko.org/pages/interfaces.html) interface는 object의 type을 체크해주는 ts의 고유한 문법이다. object 각 요소..

darrengwon.tistory.com

 

어쨌거나 Go는 덕타입을 지원하므로 struct에 굳이 implement를 명시하여 특정 interface 에 속한다고 알릴 필요 없이, 특정 메서드를 사용하면, 어느 인터페이스인지 알게 된다. 이를 "내포된 인터페이스"라 한다.

 

간단히 인터페이스를 활용해보았다.

인터페이스의 실전적인 사용례는 "특정 인터페이스에만 해당하는 타입만 함수를 이용할 수 있게 한다"라고 이해하는게 편하더라구요.

 

여튼, 아래 코드를 살펴보면 Area 메서드를 가진 구조체는 모두 Figure 인터페이스 타입에 해당하고

해당 타입이기만 하면 구조체별로 메서드를 전부 다 만들어주는 것이 아니라 추상화 인터페이스를 기반으로 작동하는 함수를 만들어낼 수 있습니다.

type Figure interface {
	Area() float32
}

type Square struct {
	width float32
	height float32
}

type Triangle struct {
	width float32
	height float32
}

func (s Square) Area() float32 {
	return s.width * s.height
}

func (s Triangle) Area() float32 {
	return s.width * s.height / 2
}

func showArea(f Figure) {
	fmt.Println(f.Area())
}

func main() {
	s := Square{12.2, 53.3}
	t := Triangle{12.2, 53.3}
	showArea(s)
	showArea(t)
}

 

 

왜 point receiver를 사용하면 에러가 나는 것일까?

 

그런데 의문인 점이 생깁니다. 위 코드에서는 왜 메서드에 point receiver를 사용하지 않고 value receiver를 사용한 것일까요?

실제로, 메서드에 point recevier를 할당하면 아래와 같은 에러가 발생합니다.

cannot use s (variable of type Square) as Figure value in argument to showArea: missing method Area (Area has pointer receiver)

번역하자면, Square 구조체인 s를 showArea 함수의 인자인 Figure의 값으로 사용할 수가 없습니다. Area 메서드가 없습니다. Area는 pointer receiver를 사용하고 있습니다. 

 

결론적으로 말하면, value receiver도 되고 point receiver도 됩니다.

단지 코드를 아래처럼 고쳐줘야할 뿐이죠. 인터페이스의 변수이자 구조체에 해당하는 값(s)를 생성할 때 포인터를 할당하면 됩니다.

type Figure interface {
	Area() float32
}

type Square struct {
	width float32
	height float32
}

// pointer receiver 
func (s *Square) Area() float32 {
	return s.width * s.height
}

func showArea(f Figure) {
	fmt.Println(f.Area())
}

func main() {
    // pointer
	s := &Square{12.2, 53.3}
	showArea(s)
}

 

 

포함된 인터페이스

 

Read, Close 메서드만 가지면 Reader고, Write, Close 메서드만 가지면 Writer다.

Read, Write, Close 모두 다 가져야 ReadWriter 인터페이스 타입에 해당한다.

type Reader interface {
	Read() (n int, err error)
	Close() error
}

type Writer interface {
	Write() (n int, err error)
	Close() error
}

// Reader, Wrtier 인터페이스의 메서드 집합을 모두 포함함.
// Read, Write, Close 메서드 다 가짐
type ReadWriter interface {
	Reader
	Writer
}

 

 

Empty Interface (빈 인터페이스)

 

생각해보면, 메서드를 가지고 있지 않은 인터페이스는 모든 타입이 빈 인터페이스에 해당하지 않을까요? 맞습니다.

아래는 Go 내부적으로 구현된 fmt.Println의 정의입니다. 인자로 받는 a가 interface{} 즉, 빈 인터페이스인 것을 확인해볼 수 있습니다.

Println(a ...interface{}) (n int, err error)

 

비슷하게 우리도 빈 인터페이스를 활용해봅시다. 어떤 타입이던 타입과 값을 출력하는 함수를 만들어보았습니다.

커스텀 타입인 HotDogType도 별다른 변환 과정 없이 잘 출력하는 것을 확인할 수 있습니다.

func explain(a interface{}) {
	fmt.Printf("type is %T, value is %v\n", a, a)
}

type HotDogType string
type Person struct {
	Name string
}

func main() {
	var a HotDogType = "hotdog"
	b := Person{Name: "darren"}
	explain(a)
	explain(b)
}

 

 

 

 

ref)

https://velog.io/@kykevin/Go%EC%9D%98-Interface#value-vs-pointer-receiver

https://kamang-it.tistory.com/entry/Go16%EB%A9%94%EC%86%8C%EB%93%9C%EC%99%80-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체