[Golang] Method

2021-11-12 hit count image

Golang에서 Method(메서드)를 정의하고 사용하는 방법에 대해서 알아봅시다.

개요

이번 블로그 포스트에서는 Golang의 Method(메서드)에 대해 자세히 알아보고 사용하는 방법에 대해서 살펴보려고 합니다. 이 블로그 포스트에서 소개하는 코드는 다음 링크를 통해 확인하실 수 있습니다.

메서드

Golang에서 메서드는 함수의 한 종류로써, 타입에 속한 함수를 메서드라고 합니다. 함수는 일반적으로 타입과 독립적으로 존재하지만, 메서드는 타입에 종속되게 됩니다. Golang에서는 어떤 타입도 메서드를 가질 수 있습니다.

Golang에서 메서드는 다음과 같이 정의합니다.

// func 리시버 메서드명
func (r Person) greeting() string {
  return r.width * r.height
}

리시버에는 패키지의 모든 지역 타입(패키지 안에 선언된 타입)이 가능하며, 메서드는 리시버에 종속된 함수가 됩니다.

이를 확인하기 위해 main.go 파일을 생성하고 다음과 같이 수정합니다.

package main

import "fmt"

type Person struct {
  Name string
  Age  int
}

func (r Person) greeting() string {
  return "Hello, " + r.Name
}

func main() {
  p := Person{Name: "Rob", Age: 4}
  fmt.Println(p.greeting())
}

이를 실행하면 다음과 같은 결과가 출력됩니다.

# go run main.go
Hello, Rob

객체지향 프로그래밍

Golang은 객체지향 프로그래밍 언어(OOP, Object Oriented Programming language)입니다. 여기서 객체(Object)는 데이터(State)와 기능(Function)을 가지는 것을 의미합니다.

만약, Golang에 메서드가 존재하지 않으면, 데이터(타입)과 기능(함수)이 따로 존재하게 됩니다. 그렇면 Golang에서 객체를 생성할 수 없으며, 객체 지향 프로그래밍을 할 수 없게 됩니다.

Golang에서는 메서드를 통해, 데이터에 종속된 기능들을 구현할 수 있게 되어 객체지향 프로그래밍을 할 수 있게 됩니다. 또한, 메서드는 다음과 같이 데이터간의 관계도 정의할 수 있습니다.

// Student <- Enroll -> Course
func (s * Student) Enroll(c *Course) {
  ...
}

// Student <- SendReport -> Professor, Report
func (s * Student) SendReport(p *Professor, r *Report) {
  ...
}

물론, 함수를 사용해도 메서드와 동일한 기능을 구현할 수 있습니다. 기능상으로는 함수와 메서드는 동일합니다. 다만, 함수는 주어진 데이터를 단순히 연산만 하는 반면, 메서드는 데이터에 종속되어 있다는 개념의 차이만 존재합니다.

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

Golang으로 프로그래밍을 하다보면, 포인터 타입 메서드와 값 타입 메서드가 존재하는 것을 확인할 수 있습니다. Golang에서는 주로 타입의 성격에 따라 포인터 타입 메서드와 값 타입 메서드는 메서드를 구별해서 사용하게 됩니다.

메서드를 사용하였을 때, 새로운 객체를 생성할 필요가 있으면, 값 타입 메서드를 사용하게 되며, 새로운 객체를 생성할 필요가 없을 때 포인터 타입 메서드를 사용하게 됩니다.

type Person struct {
  Name string
  Age  int
}

func (p *Person) AddAge() {
  p.Age += 1
}

예를 들어 Person이라는 타입이 있습니다. Person 타입으로 만든 사람 데이터의 나이가 한 살 증가하더라도, 해당 사람은 동일한 사람이여야 하므로, 포인터 타입 메서드를 사용하게 됩니다.

type Temperature float64

func (t Temperature) Up(temp float64) Temperature {
  return t + Temperature(temp)
}

반대로 Temperature라는 타입은 온도가 올라가면, 이전 온도와 상승된 온도는 개념적으로 다르므로 값 타입 메서드를 사용하는 것이 적합합니다.

이를 확인하기 위해 main.go 파일을 다음과 같이 수정합니다.

package main

import "fmt"

type Person struct {
  Name string
  Age  int
}

func (p *Person) AddAge() {
  p.Age += 1
}

type Temperature float64

func (t Temperature) Up(temp float64) Temperature {
  return t + Temperature(temp)
}

func main() {
  p1 := Person{Name: "Rob", Age: 4}

  fmt.Println("P1: ", p1)
  p1.AddAge()
  fmt.Println("P1: ", p1)

  t := Temperature(30.0)

  fmt.Println("T: ", t)
  t = t.Up(4.0)
  fmt.Println("T: ", t)
}

이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

# go run main.go
P1:  {Rob 4}
P1:  {Rob 5}
T:  30
T:  34

생성자와 소멸자

Golang에는 생성자와 소멸자가 존재하지 않습니다. 따라서 생성자가 필요한 경우, 패키지에서 생성자처럼 동작하는 함수를 만들어 제공해야 합니다.

이를 확인하기 위해 group/main.go 파일을 만들고 다음과 같이 수정합니다.

package group

type Member struct {
  Name string
  Age  int
}

func NewMember(name string, age int) *Member {
  return &Member{Name: name, Age: age}
}

여기서 NewMember 함수가 생성자 역할을 하는 함수에 해당합니다.

그런 다음 다음 명령어를 실행하여, 모듈을 생성합니다.

go mod init github.com/dev-yakuza/study-golang/method/group
go mod tidy

그런 다음, 이를 실제로 사용할 main 패키지를 생성해 봅시다. group 폴더와 동일한 위치에서 main/main.go 파일을 생성하고 다음과 같이 수정합니다.

package main

import (
  "fmt"

  "github.com/dev-yakuza/study-golang/method/group"
)

func main() {
  member1 := group.Member{Name: "John", Age: 20}
  member2 := group.NewMember("Paul", 30)

  fmt.Println(member1)
  fmt.Println(member2)
}

그리고 다음 명령어를 사용하여 모듈을 생성하고, 로컬 모듈을 연결합니다.

go mod init github.com/dev-yakuza/study-golang/method/main
go mod edit -replace github.com/dev-yakuza/study-golang/method/group=../group
go mod tidy

그리고 다음 명령어를 사용하여 생성한 프로그램을 실행해 봅니다.

go run main.go

그럼 다음과 같은 결과를 확인할 수 있습니다.

{John 20}
&{Paul 30}

Golang에서 모듈을 사용하는 방법에 대해서는 아래에 블로그 포스트를 참고하시기 바랍니다.

Embedded field 메서드

Golang의 구조체는 Embedded field를 가질 수 있습니다. Embedded field에 관한 자세한 내용은 아래에 블로그 포스트를 참고하시기 바랍니다.

다음과 같이 구조체가 Embedded field를 가질 수 있으며, Embedded field의 구조체가 다음과 같이 메서드를 가지고 있을 수 있습니다.

type Member struct {
  Name string
  Age  int
}

func (p *Member) AddAge() {
  p.Age += 1
}

type Group struct {
  Member
  Grade int
}

이런 경우, 다음과 같이 Embedded field의 메서드를 사용할 수 있습니다.

g := Group{Member: Member{Name: "John", Age: 20}, Grade: 1}
g.AddAge()
fmt.Println(g)

이를 확인하기 위해 main.go 파일을 다음과 같이 수정합니다.

package main

import "fmt"

type Member struct {
  Name string
  Age  int
}

func (p *Member) AddAge() {
  p.Age += 1
}

type Group struct {
  Member
  Grade int
}

func main() {
  g := Group{Member: Member{Name: "John", Age: 20}, Grade: 1}
  fmt.Println(g)
  g.AddAge()
  fmt.Println(g)
}

그리고 이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.


# go run main.go
{{John 20} 1}
{{John 21} 1}

완료

이것으로 Golang에서 메서드를 정의하고 사용하는 방법에 대해서 알아보았습니다. 또한, 메서드의 특징과 생성자를 만드는 방법에 대해서도 알아보았습니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

책 홍보

스무디 한 잔 마시며 끝내는 React Native 책을 출판한지 벌써 2년이 다되었네요.
이번에도 좋은 기회가 있어서 스무디 한 잔 마시며 끝내는 리액트 + TDD 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.

스무디 한 잔 마시며 끝내는 React Native, 비제이퍼블릭
스무디 한 잔 마시며 끝내는 리액트 + TDD, 비제이퍼블릭
Posts