본문 바로가기

[Go] 메소드 본문

Dev

[Go] 메소드

겨울바람_ 2024. 11. 9. 19:05

Method

이전에 Java를 사용해본 사람이라며 Method라는 이름이 친숙하게 느껴질 것이다. 필자 또한 Java를 주로 사용했기 때문에 이번 파트를 공부하며 Method라는 명칭이 굉장히 반갑게 느껴졌다.

 

하지만 Go는 Java와 달리 클래스가 존재하지 않기 때문에 어떤 식으로 메소드를 사용하는지 의구심이 들었다.

 

Go에서의 메소드는 구조체 바깥에서 정의되며 리시버를 통해 구조체와 연결된다.

Receiver

메소드는 구조체 바깥에서 선언되기 때문에 메소드가 어떤 구조체에 속하는지 표시할 방법이 필요하다. 이를 위해 리시버를 사용하여 메소드가 속한 구조체를 알려준다.

🚀Method 선언

메소드를 선언하기 위해서는 리시버를 ()로 명시해야 한다.

func (r Rabbit) info() int {
    return r.width * r.height
}

위의 코드에서 (r Rabbit)부분이 리시버다. 리시버를 통해 우리는 info() 메소드가 Rabbit 타입에 속해있다는 것을 알 수 있다. 구조체 변수로 선언된 r은 메소드 내에서 매개변수처럼 이용된다.

 

모든 로컬 타입은 리시버 타입이 될 수 있으며, 별칭 타입 또한 리시버가 될 수 있다. int 타입 또한 별칭 타입을 만들면 메소드를 가질 수 있다.

package main

import "fmt"

type myInt int

func (a myInt) add(b int) int {
    return int(a) + b
}

func main() {
    var a myInt = 10
    fmt.Println(a.add(30)) // 40
    var b int = 20
    fmt.Println(myInt(b).add(50)) // 70
}

포인터 메소드 vs 값 타입 메소드

package main

import "fmt"

type account struct {
    balance int
    firstName string
    lastName string
}

func (a1 *account) withdrawPointer(amount int) {
    a1.balance -= amount
}

func (a2 account) withdrawValue(amount int) {
    a2.balance -= amount
}

func (a3 account) withdrawReturnValue(amount int) account {
    a3.balance -= amoun
    return a3
}

func main() {
    var mainA *account = &account{ 100, "Joe", "Park" }
    mainA.withdrawPointer(30)
    fmt.Println(mainA.balance) //70

    mainA.withdrawValue(20)
    fmt.Println(mainA.balance) // 70

    var mainB account = mainA.withdrawReturnValue(20)
    fmt.Println(mainB.balance) // 50

    mainB.withdrawPointer(30)
    fmt.Println(mainB.balance) // 20    
}

위의 코드에서 withdrawPointer() 메소드는 리시버로 포인터를 갖기 때문에 *account 타입에 속하고, withdrawValue(), withdrawReturnValue() 메소드는 값 타입을 리시버로 갖기 때문에 account에 속한다.

 

포인터 메소드를 호출하면 포인터가 가리키고 있는 메모리의 주소값이 복사되지만, 값 타입 메소드를 호출하면 리시버 타입의 모든 값이 복사된다.

 

withdrawPoint() 메소드가 호출되면 mainA 포인터 변수가 갖는 값인 메모리 주소가 복사되기 때문에 a1mainA는 동일한 인스턴스를 가리키게 된다. 그렇기 때문에 a1balance를 변경하게 되면 mainAbalance 또한 변경된다.

 

반면, withdrawValue() 메소드를 호출할 때는 값이 복사되어 전달되기 때문에 a2mainA는 서로 다른 메모리 주소를 갖게 된다.

 

그렇기 때문에 a2balance를 변경해도 mainAbalance는 변경되지 않는다. 이를 해결하기 위해서 withdrawReturnValue() 메소드처럼 변경된 값을 다시 반환해야 한다.

 

withdrawReturnValue() 메소드에서는 값 복사가 호출 시와 결과값 반환 시 두 번 이루어진다. 이는 mainA, a3, mainB가 각각 다른 메모리 주소를 갖게 된다는 것을 의미한다.

메소드 호출

mainA는 포인터 변수이고, withdrawValue()account 값 타입을 리시버로 받는 메소드다. 포인터인 mainA는 바로 호출할 수 없고 (*main).withdrawValue(20)과 같이 값 타입으로 변환하여 호출하여야 한다.

 

Go에서는 자동으로 mainA의 값으로 변환하여 호출한다. 반대의 경우에도 동일한데, mainB.withdrawPointer(30)에서 mainBaccount 값 타입 변수이고 withdrawPointer()*account 포인터를 리시버로 받는 메소드다.

 

동일하게 바로 호출이 불가능하기 때문에 주소 연산자를 사용해 (&mainB).withdrawPointer(30)처럼 포인터로 변환 후 호출해야 하지만 Go에서는 자동으로 mainB의 메모리 주소값으로 변환하여 호출한다.

'Dev' 카테고리의 다른 글

[Go] 함수 고급  (0) 2024.12.02
[Go] 인터페이스  (3) 2024.11.10
[Go] 슬라이스  (0) 2024.11.07
그림으로 이해하는 객체 지향 설계 5원칙 [SOLID]  (2) 2024.07.20
CPU-Scheduling  (2) 2024.04.07
Comments