본문 바로가기
프로그래밍/golang

ultimate in go (4)- data structure(interface)

by 나도한강뷰 2023. 1. 23.

interface

값이 없는 타입

type reader interface {
    read(b []byte) (int, error) // (1)
}
type reader interface {
    read(i int) ([]byte, error) // (2)
}
  • []byte값을 통해서 반환하게 되면 메소드 호출마다 slice를 만들게 되고 반복적으로 힙메모리에 저장하게 됨으로 효율이 좋지 못하다

concrete type

  • 매소드를 가질 수 있는 모든 타입/
  • 사용자 정의 타입만이 메소드를 가질수 있다.
    • 남이 만들어 놓은 타입을 동일 패키지내에서 선언없이는 메소드를 가질 수 없음

인터페이스를 이용하여서 메소드를 호출할때, 메소드와 인터페이스간의 관계

type file struct {
    name string
}
type pipe struct {
    name string
}
func (file) read(b []byte) (int, error) {
    s := "<rss><channel><title>Going Go Programming</title></channel></rss>"
    copy(b, s)
    return len(s), nil
}
func (pipe) read(b []byte) (int, error) {
    s := `{name: "hoanh", title: "developer"}`
    copy(b, s)
    return len(s), nil
}
f := file{"data.json"}
p := pipe{"cfg_service"}

func retrieve(r reader) error {
    data := make([]byte, 100)

    len, err := r.read(data)
    if err != nil {
        return err
    }

    fmt.Println(string(data[:len]))
    return nil
}
retrieve(f)
retrieve(p)
  • 이런 상황에서 f와 reader라는 interface는 어떻게 작동하는가
    • reader interface는 2개의 word를 가지고 있고, 첫번째 word는 itable이라는 값을 가르키고
    • 두번째 word는 f의 복사본을 가르킨다.
    • itable의 첫번째값은 저장된 데이터 타입을 의미
    • 두번째 값은 함수 포인터의 모체, 인터페이스를 통해서 정확한 메소드를 호출하기위해서 사용
  • 이렇게 f와 p는 각각 다른 read를 호출하게 되는데, 둘의 메소드 구현은 완전히 디커플링 되어있고,
  • retrieve가 interface를 통해서 값을 받는것은 go에서 보여주는 최고 수준의 디커플링이다.

포인터 리시버를 이용한 인터페이스

type notifier interface {
    notify()
}
type printer interface {
    print()
}
type user struct {
    name  string
    email string
}
func (u user) print() {
    fmt.Printf("My name is %s and my email is %s\n", u.name, u.email)
}
func (u *user) notify() {
    fmt.Printf("Sending User Email To %s<%s>\n", u.name, u.email)
}
func (u *user) String() string {
    return fmt.Sprintf("My name is %q and my email is %q", u.name, u.email)
}

u := user{"Hoanh", "hoanhan@email.com"}

type duration int

func (d *duration) notify() {
    fmt.Println("Sending Notification in", *d)
}

duration(42).notify()

func sendNotification(n notifier) {
    n.notify()
}
  • sendNotification(u)를 하면, 에러가나온다. 왜냐하면 u는 포이터 메소드였기 때문이다.
  • 값 리시버 매소드
    • 이건 값으로도, 포인터로도 사용 가능하다
  • 포인터 리시버 매소드
    • 이건 포인터로만 사용 가능하다
  • 앞서서 매소드를 사용할때 문제가 없었던것은, 구체적인 값을 이용하여서 호출하기때문인데,
  • 인터페이스를 통해서 매소드를 호출할때는 구체적인 값을 통해서 호출하는 것이 아니기때문에
  • 문제가 생길 수 있으므로 그것을 방지하는 것이다.
  • 그것은 duration(42).notify()같은 경우에, 값은 있지만, 주소값은 없는 형태가 된다.
  • 그러므로 포인터가 없기때문에 에러가 발생하는 가능성이 생기고, 그에따라서 포인터로 선언된 매소드는
  • 포인터로만 사용할 수 있다(인터페이스를 통하는 경우)