Go의 메서드에선 Receiver가 왜 필요한가?
여러 언어(java, kotlin, js ...)는 클래스 내부에 메서드를 명시합니다.
javascript의 경우 class 내부에 메서드를 명시할 수 있습니다. 아래와 같이요.
class Student {
constructor() { ... }
sayHello() {
console.log("hello")
}
}
그러나 Go에서는 구조체의 밖에 메서드가 있습니다. 따라서 특정 메서드가 어느 구조체의 것인지를 표시할 방법으로 Receiver를 사용합니다.
다음은 area 라는 이름의 메서드이고 s Square 부분이 Receiver(리시버)입니다.
그러니까 area 메서드는 Square 구조체의 메서드라는 것이죠.
func (s Square) area() int {
return s.width * s.height
}
일반 함수와 비슷하게 생겨서 헷갈리는데, func와 함수 이름 사이에 리시버가 있으면 메서드, 아니면 일반 함수라고 생각하시면 됩니다.
근데 왜 굳이 메서드를 만들어야 할까요?
package main
import "fmt"
type account struct {
balance int
}
// 일반 함수
func withdrawFunc(a *account, amount int) {
a.balance -= amount
}
// 메서드
func (a *account) withdrawMethod(amount int) {
a.balance -= amount
}
func main() {
a := &account{100}
// 함수 형태 호출
withdrawFunc(a, 30)
fmt.Println(a.balance) // 70
// 메서드 형태 호출
a.withdrawMethod(30)
fmt.Println(a.balance) // 40
}
method를 설정할 때 주의할 점
주의할 점은, struct의 주소가 아닌 일반 struct를 receiver로 넘기게 되면 메서드를 사용하는 struct가 아닌 struct 의 복사본에 내용을 적용합니다. 다시 말하지면 Go는 함수의 인자를 call by value로 받기 때문에 구조체를 그냥 넣으면 안됩니다.
func (a account) withdrawMethod(amount int) {
// 아래의 경우 receiver는 해당 메서드를 이용하는 struct가 아닌 해당 struct의 복사본입니다.
// 따라서 아래 조작은 해당 함수의 스코프를 벗어나면 아무 의미가 없음.
a.balance -= amount
}
결론적으론 포인터를 이용하여 구조체를 조작해야 합니다. 이를 'pointer receiver'라고 부릅니다.
func (a *account) withdrawMethod(amount int) {
a.balance -= amount
}
별칭 리시버 타입
별칭 타입도 리시버가 될 수 있습니다.
type HotDogInt int
func (a HotDogInt) add(b int) int {
return int(a) + b
}
func main() {
var a HotDogInt = 10;
output := a.add(5)
fmt.Println(output) // 15
}
String 함수
조금 특수한 형태의 메서드를 알아보겠습니다. python에서 class를 그냥 출력하면 __str__ 값이 반환됩니다.
Go에도 비슷한 기능의 메서드가 존재합니다.
func (receiver *Account) String() string {
return "this is String method!"
}
func main() {
account := accounts.NewAccount("darren")
fmt.Println(account) // &{darren 10}이 아니라 "this is String method!" 출력
}
Error 핸들링
아래와 같은 메서드를 작성했다고 가정합니다.
func (receiver *Account) Withraw(amount int) error {
if receiver.balance < amount {
return errors.New("Can't withraw")
}
receiver.balance -= amount
// 반환이 error이므로 아무 반환도 하고 싶지 않더라도 nil 반환해야 함
return nil
}
아래와 같이 err를 분리하여 필요할 때 넣어 편리하게 에러를 쓸 수 있습니다.
var errNoMoney = errors.New("no money. you can't withraw")
// Withraw method
func (receiver *Account) Withraw(amount int) error {
if receiver.balance < amount {
return errNoMoney
}
receiver.balance -= amount
// 반환이 error이므로 아무 반환도 하고 싶지 않더라도 nil 반환해야 함
return nil
}
그러나 error가 나야할지라도 에러를 일으키지 않고 조용히 실패합니다.
func main() {
account := accounts.NewAccount("darren")
account.Deposit(10)
fmt.Println(account.Balance())
account.Withraw(20) // silently fail
fmt.Println(account.Balance())
}
만약 에러가 발생했을 때 block하고 싶다면 아래와 같이 해야 합니다.
func main() {
account := accounts.NewAccount("darren")
account.Deposit(10)
fmt.Println(account.Balance())
err := account.Withraw(20)
if err != nil {
log.Fatalln(err)
}
fmt.Println(account.Balance())
}
try/except 같은 구문은 없습니다. 위 방식에 익숙해집시다.
'Programming Language > 🐿️ Go (Golang)' 카테고리의 다른 글
Go func (2) : defer, 함수 리터럴(익명함수), 값으로서의 함수, callback, closure (0) | 2021.05.15 |
---|---|
Go func (1) : pass by reference, 가변 인자 함수, Multiple return, naked return (0) | 2021.05.15 |
struct (1) : struct(구조체)를 생성, 메모리 패딩, struct factory (2) | 2021.05.15 |
Go의 기초 문법 (5) : slice(동적 배열). cap에 대한 이해 (0) | 2021.05.15 |
Go의 기초 문법 (4) : array, map (0) | 2021.05.14 |