Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.
메서드(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 타입에 속함.
→ 포인터 메서드를 호출하면 포인터가 가리키고 있는 메모리의 주솟값이 복사됨.
→ 값 타입 메서드를 호출하면 리시버 타입의 모든 값이 복사됨.
→ 리시버 타입이 구조체이면 구조체의 모든 데이터가 복사됨.
'Language > Golang' 카테고리의 다른 글
Golang (Go언어) 함수고급편 (1) | 2024.11.29 |
---|---|
Golang (Go언어) 인터페이스(Interface) (0) | 2024.11.27 |
Golang (Go언어) 슬라이스(Slice) 2/2 (2) | 2024.11.20 |
Golang (Go언어) 슬라이스(Slice) 1/2 (0) | 2024.11.19 |
Golang (Go언어) 패키지(Package) (6) | 2024.11.09 |