15.3. 프로그램언어 고(Go)에서의 구조체와 메소드

프로그램언어 고(Go)에서의 구조체에 메소드 연결하기

프로그램언어 고(Go)에서 구조체에 메소드를 연결하는 방법은 구조체 타입을 receiver로 하여 메소드를 정의하는 방법입니다.

이를 통해 구조체 인스턴스에 대한 메소드를 구현할 수 있습니다. 예를 들어 다음과 같은 구조체가 있다고 할 때,

type User struct {
  Name string
  Email string
}

이 User 구조체에 대한 메소드를 다음과 같이 정의할 수 있습니다.

func (u User) Notify() {
  fmt.Println("Sending User email to", u.Email) 
}

여기서 User가 바로 receiver로, 이를 통해 User 구조체 인스턴스에서 해당 메소드를 호출할 수 있게 됩니다.

user := User{"John", "john@email.com"}
user.Notify() // "Sending User email to john@email.com"

위 예제의 Notify() 메소드는 User 구조체 인스턴스에 대한 메소드로 정의되어 있습니다.

따라서 구조체 인스턴스를 통해 메소드를 호출할 수 있고, 구조체 fields와 메소드가 한 인스턴스 안에서 유기적으로 연결되어 동작합니다.

이를 통해 객체지향 프로그래밍의 특성을 가진 코드를 작성할 수 있으며, 캡슐화, 은닉화 등의 효과를 낼 수 있습니다.

주의할 점은 메소드는 항상 포인터 receiver를 사용하는 것이 안전하다는 것입니다.

func (u *User) Notify() {
  fmt.Println("Sending User email to", u.Email)
}

값 receiver인 경우 실수로 인스턴스 복사본을 수정하여 의도치 않은 결과가 발생할 수 있기 때문입니다.

이처럼 고(Go)의 구조체에 메소드를 연결하는 것은 객체지향 코드를 작성하는 데 있어 매우 중요한 패턴이므로 꼭 숙지하시기 바랍니다.

프로그램언어 고(Go)에서의 구조체 메소드와 함수의 차이

고(Go)언어의 구조체 메소드와 함수의 차이에 대해 설명 드리겠습니다.

구조체 메소드는 구조체 타입에 속한 함수입니다. 구조체를 receiver로 받아서 메소드로 구현합니다.


type person struct {
    name string
    age  int
}

func (p person) speak() {
    println(p.name, "이 말을 합니다.") 
}

위 예제 코드에서 person 구조체에 speak() 메소드를 구현한 것을 볼 수 있습니다.

반면 함수는 특정 타입과 연관되지 않은 독립된 함수를 말합니다.


func speak(p person) {
    println(p.name, "이 말을 합니다.")
} 

위와 같이 person 구조체를 파라미터로 받아서 출력하는 speak 함수가 있다면 이는 독립된 함수입니다.

구조체 메소드와 함수의 가장 큰 차이점은 메소드는 해당 구조체의 receiver를 인자로 받습니다. 반면 함수는 그런 제약이 없다는 것입니다.

구조체 메소드를 사용하면 해당 구조체 타입과 密切한 연관이 있는 메소드를 구현할 수 있고, 구조체 포인터도 receiver로 사용할 수 있어 메모리 절약 측면에서 효율적일 수 있습니다.

반면 함수는 구조체와 독립적으로 구현하기 때문에 다양한 타입을 파라미터로 사용할 수 있고 재사용성도 높다는 장점이 있습니다.

결론적으로 구조체 메소드는 구조체 타입 전용 메소드를 구현할 때, 함수는 독립적이고 보다 유연하게 사용할 수 있습니다. 상황에 맞게 장단점을 고려하여 선택하시길 바랍니다.

최대한 친절하고 이해하기 쉽게 설명 드렸기를 바랍니다.

프로그램언어 고(Go)에서의 구조체 메소드를 활용한 캡슐화

프로그램언어 고(Go)에서 구조체 메소드를 활용한 캡슐화는 구조체의 필드를 private으로 만들고, 해당 필드에 접근할 수 있는 메소드를 제공하는 방식으로 구현할 수 있습니다.

이를 통해 구조체의 필드를 외부에서 직접 접근하지 못하도록 차단하고, 구조체에서 정의한 메소드를 통해서만 접근할 수 있도록 제한을 둘 수 있습니다. 즉 필드의 은닉과 캡슐화를 구현할 수 있습니다.

다음은 고(Go)에서 구조체 메소드를 활용한 캡슐화 예제 코드입니다.


package main

import "fmt"

type person struct {
    name string // private 필드
    age  int    // private 필드
}

func (p *person) SetName(name string) {
    p.name = name
}

func (p *person) Name() string {
    return p.name
}

func (p *person) SetAge(age int) {
    p.age = age
}

func (p *person) Age() int {
    return p.age
}

func main() {
    p := person{}

    p.SetName("홍길동") // Setter 메소드 호출
    p.SetAge(30)

    fmt.Println(p.Name()) // Getter 메소드 호출
    fmt.Println(p.Age()) 
}

person 구조체의 name과 age 필드를 private로 선언하였습니다.

이 필드에 접근할 수 있는 Setter(SetName, SetAge)와 Getter(Name, Age) 메소드를 구조체와 연결되도록 정의하였습니다.

main() 함수에서 직접 접근하지 않고 Setter 메소드를 호출하여 name과 age를 설정하였고, Getter 메소드를 호출하여 값을 가져오는 것을 확인할 수 있습니다.

이러한 방식으로 구조체의 필드를 외부에서 직접 접근하지 못하도록 막고, 공개된 메소드를 통해서만 접근할 수 있도록 구현하는 것이 고(Go)에서의 구조체 메소드를 활용한 캡슐화입니다.

프로그램언어 고(Go)에서의 포인터를 리시버로 사용하는 메소드 생성

Go언어에서 포인터를 메소드의 리시버(receiver)로 사용하면 그 메소드는 해당 포인터가 가리키는 값을 가지고 작업을 수행할 수 있습니다.

이를 통해 메소드 호출 시 매번 값의 복사본을 만들어서 전달할 필요가 없어 효율적으로 메모리를 사용할 수 있습니다.

아래 예제 코드를 통해 포인터 리시버 사용 방법을 살펴보겠습니다.


package main

import "fmt"

type myStruct struct {
    name string
}

func (m *myStruct) changeName(newName string) {
    m.name = newName  // 포인터 m을 통해 실제 구조체 값 변경
}

func main() {
    s := myStruct{"John"}
    fmt.Println(s.name) // "John"

    s.changeName("Mary")  
    fmt.Println(s.name) // "Mary" (값이 변경됨)
}

위 예제에서 myStruct 타입의 포인터를 changeName 메소드의 리시버로 선언했습니다.

이에 따라 changeName 메소드 내에서 실제 구조체 인스턴스의 name 필드 값을 변경할 수 있습니다.

main 함수에서 실제 구조체 인스턴스인 s를 생성하고, s.changeName 메소드를 호출하면 내부적으로 s의 포인터가 메소드로 전달되므로 구조체의 실제 값이 변경되는 것을 확인할 수 있습니다.

이를 통해 복사본을 전달하는 것이 아니라 실제 인스턴스를 가리키는 포인터를 사용해 효율적으로 메모리를 사용할 수 있습니다.

포인터 리시버 사용은 상태를 변경하는 메소드를 정의할 때 유용하게 사용할 수 있습니다.

프로그램언어 고(Go)에서의 메소드를 이용한 구조체의 비헤이비어 관리

Go언어에서 구조체의 메소드를 정의하여 해당 구조체의 비헤이비어를 관리할 수 있습니다. 이를 통해 객체지향프로그래밍의 특성을 Go언어의 구조체에서도 활용할 수 있습니다.

예를 들어 다음과 같이 Person 이라는 구조체를 정의하고 NewPerson으로 새 인스턴스를 만드는 메소드와 , Walk 와 Stop 이라는 메소드를 정의할 수 있습니다.


type Person struct {
    name string
}

func NewPerson(name string) *Person {
    return &Person{name: name}
}

func (p *Person) Walk() {
    println(p.name + " is walking") 
}

func (p *Person) Stop() {
    println(p.name + " stopped walking")
}

이렇게 메소드를 구조체에 추가하면 다음과 같이 해당 구조체의 인스턴스를 만들고 메소드를 호출하여 비헤이비어를 관리할 수 있습니다.


p := NewPerson("Bob")
p.Walk() // Bob is walking
p.Stop() // Bob stopped walking

구조체 뿐만 아니라 인터페이스도 메소드를 가질 수 있습니다. 인터페이스의 메소드를 구현하는 구조체를 만들면 인터페이스 타입의 변수로 해당 구조체의 인스턴스를 참조할 수 있습니다.


type Walker interface {
    Walk()
    Stop()
}

func Walk(w Walker) {
    w.Walk()
    w.Stop()
}

이처럼 Go언어의 메소드와 인터페이스를 적절히 활용하면 객체지향 언어의 특성을 Go언어의 구조체와 결합할 수 있습니다. 특히 메소드는 구조체와 강력하게 결합된 형태이기 때문에 구조체의 비헤이비어를 보다 명확하고 효과적으로 관리할 수 있습니다.

Leave a Comment