Ultimate-Go-03
메서드라는 제목의 파트이다.
고에서는 메서드 즉 리시버를 선정하는 데 있어 2가지 방법이 있다.
하나는 value receiver, pointer receiver 형태를 보자면
type user struct {
name, email string
}
func (u user) notify() {
fmt.Printf("Sending user email to %s <%s>\n", u.name, u.email)
}
func (u *user) changeEmail(email string) {
u.email = email
fmt.Printf("Changed User Email To %s\n", email)
}
어떻게 선언해서 사용하던 GO에서는 알아서 캐스팅해서 처리를 해준다.
고 내부적으로 어떻게 호출이 되는 걸까? 메서드 즉 리시버 함수는 앞에 선언된 값이 바로 첫 번째 파라미터로 동작한다는 의미이다.
u := user{"guiwoo","park.guiwoo@hotmail.com"}
u.notify() // u.notify(u)
u.changedEmail("holy") // (*u).changedEmail(&u,"holy")
// 에 표시된 부분처럼 고 내부적으로 동작한다는 의미이다.
그래서 뭐 어떻게 사용하라는 건가?
라는 의문이 들 수 있다. 일반적으로 구조체의 값 즉 필드 내부가 변경되어야 한다면 포인터 리시버를 , 그게 아닌 경우에는 값 리시버를 사용하는 게 맞다고 생각할 수 있다.
그러나 고 랜드라는 IDE를 사용하다 보면 구조체의 리시버 함수에 대해 일관성 있게 사용하라고 나온다.
다시 말해 뭐가 됐든 하나의 구조체 에는 일관된 리시버 함수를 사용하라는 말이다.
가장 큰 이유 중 하나는 바로 인터페이스에 대해 값 또는 리시버 타입에 따라 인터페이스가 충족될 수도 있고 불충족될 수도 있다.
이로 인해 인터페이스로 추상화된 코드들이 종종 깨지는 경우가 발생한다.
(실제로 프로젝트 수행 중 일관된 메서드로 변경하던 중 인터페이스가 깨지는 경우가 발생했다.)
그 외에도 사용자 입장에서 해당 함수가 값복사를 하는지, 아니면 메모리의 값을 변경하는지 혼란이 올 수 있다.
이런 고로 나는 프로젝트에서 는 대부분 * 리시버를 많이 사용하게 된다.
물론 기존에 있던 리시버 가 있다면 해당 리시버 방법을 따라가지만 만약 새로운 타입을 생성하게 된다면 포인터 리시버를 주로 사용한다.
특정 상태변경, 큰 구조체의 경우 값복사의 리소스 낭비를 이유로 포인터 리시버 가 보다 값을 관리하기 쉽다고 생각하기 때문이다.