Language/Golang

Golang (Go언어) 메서드(Method)

HeeWorld 2024. 11. 27. 16:23

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스 코트 Gopher(고퍼)

 

메서드(Method)

- 타입에 속한 함수

- 메서드 선언하려면 리시버를 func 키워드와 함수 이름 사이에 소괄호로 명시해야 함

- 리시버는 모든 패키지 지역 타입이 가능함 (구조체, 별칭 타입 등)

func (r Rabbit) info() int {   
//(r Rabbit) = 리시버, info() = 메서드 명
    return r.width * r.height
}

 

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}     // balance가 100인 account 포인터 변수 생성

	withdrawFunc(a, 30)    // 함수 형태 호출

	a.withdrawMethod(30)   // 메서드 형태 호출

	fmt.Printf("%d \n", a.balance)
}

// 결과

40

 

- 메서드를 선언하고, func 키워드와 함수명 사이에 리시버 타입과 그 리시버 값을 갖는 변수가 있으면 메서드, 없으면 함수

func (a *account) withdrawMethod(amount int) {
// (a *account) = 리시버  withdrawMethod = 메서드 명
  a.balance -= amount
}

 

✓ 메서드 정의는 같은 패키지 내 어디에도 위치할 수 있으나, 리시버 타입이 선언된 파일 안에 정의하는게 일반적인 규칙

ex) type Student struct rㅜ조체를 student.go 파일에 정의했으면, Student의 메서드들도 모두 student.go 파일에 모아 놓음.

 

- 메서드 함수를 호출하는데, 예제에서 withdrawFunc()와 withdrawMethod()는 완전히 똑같은 동작을 함.

   = a *account는 포인터 주소값을 받아서 인스턴스 balance를 마이너스 함

- withdrawFunc()는 일반 함수이고, withdrawMethod()는 메서드라 호출 방법이 상이함.

- 구조체에서 필드가 해당 구조체에 속하듯, 메서드는 해당 리시버 타입에 속함.

- withdrawMethod()는 리시버 타입인 *account 타입에 속한 메서드임.

- 구조체의 필드에 접근할 때처럼 점(.) 연산자를 사용해 해당 타입에 속한 메서드를 호출할 수 있음.

a.withdraw2(30)

 

 

별칭 리시버 타입

- 모든 로컬 타입이 리시버 타입으로 가능하기 때문에 별칭 타입도 리시버가 될 수 있고, 메서드를 가질 수 있음.

   = 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))
	var b int = 20
	fmt.Println(myInt(b).add(50))
}

// 결과

40
70

 

- int 타입의 별칭인 myInt 선언 후, myInt는 int와 이름만 다를 뿐 같은 타입이지만 메서드를 가질 수 있음.

- myInt 타입의 메서드 add()를 선언하고, 두 값을 더하고 myInt 변수 a를 선언하여 메서드 add()를 호출함.

- b 변수는 int 타입으로, myInt 타입이 int의 별칭 타입이지만 엄연히 다른 타입이기 때문에 myInt의 add() 메서드를 사용할 수 없음.

  = 별칭 타입 간 타입 변환을 지원하기 때문에 myInt 타입으로 변환 후 add() 메서드를 사용

 

 

메서드는 왜 필요한가?

- 좋은 프로그래밍이라면 결합도(Coupling)를 낮추고, 응집도(Cohesion)을 높여햐 함.

- 메서드는 데이터와 관련 기능을 묶기 때문에 코드 응집돌르 높이는 중요한 역할을 함.

- 응집도가 낮으면 새로운 기능을 추가할 때 흩어진 모든 부분을 검토하고 수정해야 하는 *산탄총 수술 문제가 발생함.

   * 산탄총 수술 문제: 작은 변화에도 산탄총을 맞은 듯 많은 코드 영역을 수정하는 경우

- 코드 수정 범위가 늘어나면 관리가 복잡해지기 때문에 예기치 못한 실수가 늘어나고 더 많은 버그를 만들게 됨

   그래서 흩어진 코드 조각들을 관련된 데이터로 한 곳에 묶어 응집도를 높일 필요가 있음.

 

예) 통장을 입금과 출금이라는 기능과 묶고, 게임 속 플레이어를 스킬 사용과 아이템 획득이라는 기능과 묶어두면 응집도가 높아짐

 

객체지향: 절차 중심에서 관계 중심으로 변화

- 객체(Object)란, 데이터(State)와 기능(Function)을 묶은 것

- 메서드가 등장하기 전에는 절차 중심의 프로그래밍이었으며, 코드 설계 시 기능 호출 순서를 나타내는 순서도(flowchart)를 중요시함.

- 메서드라는 기능이 생기고 데이터와 기능이 묶인 단일 객체(object)로써 동작하게 됨.

   = 객체란, 데이터와 기능을 갖는 타입을 말하고 이 타입의 인스턴스를 객체 인스턴스(Object Instance)라고 함.

- 객체 인스턴스들이 서로 유기적으로 소통하고 관계 맺게 됨에 따라 절차보다 객체 간 관계 중심으로 패러다임이 변화함.

 

✓ 이를 객체지향 프로그래밍(Object oriented programming) 줄여서 OOP라고 부름.

- 이제는 객체 간의 관계를 나타내는 클래스 다이어그램(class diagram)을 더 중시하게 됨.

 

 

포인터 메서드 와 값 타입 메서드

- 리시버를 값 타입과 포인터로 정의할 수 있음.

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 -= amount
	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 출력
}

// 결과

70
70
50
20

 

- withdrawPointer() 메서드는 리시버로 포인터를, withdrawValue(), withdrawReturnValue() 메서드는 값 타입을 갖음.

   = withdrawPointer() 메서드는 *account 타입에 속함.

   = withdrawValue()와 withdrawReturnValue()는 account 타입에 속함.

 

→ 포인터 메서드를 호출하면 포인터가 가리키고 있는 메모리의 주솟값이 복사됨.

→ 값 타입 메서드를 호출하면 리시버 타입의 모든 값이 복사됨.

→ 리시버 타입이 구조체이면 구조체의 모든 데이터가 복사됨.