Golang3

솔다·2023년 1월 31일
0

Go 메서드(Method)

GO 언어는 객체지향 프로그래밍(OOP)을 고유의 방식으로 지원하는데, 타 언어와는 달리, GO 언어에서는 struct만 필드를 가지고, 메서드는 별도로 분리되어 정의된다.

Go의 메서드는 특별한 형태의 func 함수이다.

go는 func 키워드와 함수명 사이에 '그 함수가 어떤 struct를 위한 메서드인지'를 표시하게 된다. receiver로 불리며, struct type과 변수명을 지정하는데, 이 변수명이 입력 파라미터처럼 사용된다. 아래에 예시가 있다.

package main
 
//Rect - struct 정의
type Rect struct {
    width, height int
}
 
//Rect의 area() 메소드
func (r Rect) area() int {
    return r.width * r.height   
}
 
func main() {
    rect := Rect{10, 20}
    area := rect.area() //메서드 호출
    println(area)
}

width와 height 가 있는 Rect struct가 있고, 그 아래에 메소드가 있다.

main()을 보면, 직접 해당 구조체의 변수에서 .area()로 직접 호출하는 것을 볼 수 있다.

Value receiver는 struct의 데이타를 복사(copy)하여 전달하며, 포인터 receiver는 struct의 포인터만을 전달한다. Value receiver의 경우 만약 메서드내에서 그 struct의 필드값이 변경되더라도 호출자의 데이타는 변경되지 않는 반면, 포인터 receiver는 메서드 내의 필드값 변경이 그대로 호출자에서 반영된다.

위의 Rect.area() 메서드는 Value receiver를 표현한 것으로 만약 area() 메서드 내에서 width나 height가 변경되더라도 main() 함수의 rect 구조체의 필드값에는 변화가 없다.

하지만, 아래와 같이 이를 포인터 receiver로 변경한다면, 메서드 내 r.width++ 필드 변경분이 main() 함수에서도 반영되기 때문에 출력값이 11, 220 이 된다.

// 포인터 Receiver
func (r *Rect) area2() int {
    r.width++
    return r.width * r.height
}
 
func main() {
    rect := Rect{10, 20}
    area := rect.area2() //메서드 호출
    println(rect.width, area) // 11 220 출력
}

GO interface

구조체(struct)가 필드들의 집합체라면, interface는 메서드들의 집합체이다.

interface는 타입(type)이 구현해야 하는 메서드 원형(prototype)들을 정의한다. 하나의 사용자 정의 타입이 interface를 구현하기 위해서는 단순히 그 인터페이스가 갖는 모든 메서드들을 구현하면 된다.

인터페이스는 struct와 마찬가지로 type 문을 사용하여 정의한다.

type Shape interface {
    area() float64
    perimeter() float64
}

인터페이스 구현을 위해서는 아래와 같이 shpae interface를 위한 메서드들을 모두 구현하면 된다. 즉 위의 interface에 대해서는 아래와 같이 구현하면 끝이난다.

//Rect 정의
type Rect struct {
    width, height float64
}
 
//Circle 정의
type Circle struct {
    radius float64
}
 
//Rect 타입에 대한 Shape 인터페이스 구현 
func (r Rect) area() float64 { return r.width * r.height }
func (r Rect) perimeter() float64 {
     return 2 * (r.width + r.height)
}
 
//Circle 타입에 대한 Shape 인터페이스 구현 
func (c Circle) area() float64 { 
    return math.Pi * c.radius * c.radius
}
func (c Circle) perimeter() float64 { 
    return 2 * math.Pi * c.radius
}

인터페이스 사용

인터페이스를 사용하는 일반적인 예로 함수가 파라미터로 인터페이스를 받아들이는 경우를 들 수 있다. 함수 파라미터가 interface 인 경우, 이는 어떤 타입이든 해당 인터페이스를 구현하기만 하면 모두 입력 파라미터로 사용될 수 있다는 것을 의미한다.

아래 예제에서 showArea() 함수는 Shape 인터페이스들을 파라미터로 받아들이고 있는데, 따라서 Rect와 Circle 처럼 Shape 인터페이스를 구현한 타입 객체들을 파라미터로 받을 수 있다. showArea() 함수 내에서 해당 인터페이스가 가진 메서드 즉 area() 혹은 perimeter()을 사용할 수 있다.

func main() {
    r := Rect{10., 20.}
    c := Circle{10}
 
    showArea(r, c)
}
 
func showArea(shapes ...Shape) {
    for _, s := range shapes {
        a := s.area() //인터페이스 메서드 호출
        println(a)
    }
}

인터페이스 타입

Go 프로그래밍을 하다보면 흔히 빈 인터페이스(empty interface)를 자주 접하게 되는데, 흔히 인터페이스 타입(interface type)으로 불리운다. 예를 들어, 여러 표준 패키지들의 함수 Prototype을 살펴보면, 아래와 같이 빈 interface가 자주 등장함을 볼 수 있다. 빈 interface는 interface{}와 같이 표현한다. 아래에 대표적인 예시가 있다.

func Marshal(v interface{}) ([]byte, error);
 
func Println(a ...interface{}) (n int, err error);

Empty interface는 메서드를 전혀 갖지 않는 빈 인터페이스로서, Go의 모든 Ype은 적어도 0기ㅐ 의 메서드를 구현하므로, 모든 Type을 나타내기 위해 빈 인터페이스를 사용한다. 즉, 빈 인터페이스는 어떠한 타입도 담을 수 있는 컨테이너라고 볼 수 있다.(대표적인 예시로는 C/C++ 에서 쓰이는 void*와 같다고 볼 수 있다.)

아래의 예제에서 빈 인터페이스 활용예제를 보여주고 있다.

package main
 
import "fmt"
 
func main() {
    var x interface{}
    x = 1 
    x = "Tom"
 
    printIt(x)
}
 
func printIt(v interface{}) {
    fmt.Println(v) //Tom
}

본래라면 x에 1을 넣었다가 Tom이라는 문자열은 안들어가겠지만, 실행결과는 무리없이 "Tom"을 출력하는 걸 확인해볼 수 있었다.

Type Assertion

interface type의 X와 타입 T에 대하여 x.(T)로 표현했을 때, 이는 x가 nil이 아니며, x는 T타입에 속한다는 점을 확인(assert)하는 것으로 이러한 표현을 "Type Assertion"이라 부른다. 만약 x가 nil이거나 x의 타입이 T가 아니라면, 런 타임 에러가 발생할 것이고, x가 T타입인 경우에는 T타입의 x를 리턴한다. 즉, 아래의 예제에서는 변수 ja.(int)로부터 int형 변수 j 가 되어서 1을 출력한다.

func main() {
    var a interface{} = 1
 
    i := a       // a와 i 는 dynamic type, 값은 1
    j := a.(int) // j는 int 타입, 값은 1
 
    println(i)  // 포인터주소 출력
    println(j)  // 1 출력
}

외에도 알아야 하는 내용들이 더 있는데, 이 부분은 나중에 추가적으로 정리하도록 하자..

(참고:예제로 배우는 Go 프로그래밍-interface)

0개의 댓글