반응형

golang은 타입메소드, 인터페이스를 지원하며 타입과 관련된 어써션, 리플렉션을 지원한다.

 

타입메소드와 인터페이스는, golang이 지원해 주지 않는 개체지향 프로그래밍을 어느 정도 가능하게 해주며, 어써션과 리플렉션은 함수를 일반화하여 타입들을 사용하는데 도움을 준다.

* 타입 메소드

 타입 메소드는 특수한 수신자를 받는 함수이다. 이 함수는 다음과 같이 메소드 수신자를 함수 이름 앞에 기술해 준다.

func (method receiver) funcName(parameters) returnValues

 예를 들어 다음과 같이 struct type 과 해당 struct 필드를 조작하는 메소드를 생성할 수 있다.

type IntNum struct {
    a int
}
func (r IntNum) add(b int) (result int){
    result=r.a+b
    return result
}

add함수는 IntNum 구조체의 a에 접근 가능하며, 다른 oop언어의 this나 self와 비슷하게 동작하지만, 보통 하나의 문자로 표현되며 특수한 키워드(this, self)로 사용되지 않는다. 이 예제에서 r이 메소드의 수신자가 되는 것이다. 

 

* 인터페이스

 golang의 interface type은 메소드의 형태를 정의한 타입이다. 형식만 기술해 두고 실제 구현은 struct 타입의 메소드에서 구현이 되는 것이다. 형태가 기술되어 있어 함수의 매개변수를 인터페이스로 정의하면 이 인터페이스를 구현한 어떤 타입도 해당 함수로 전달 될 수 있다. 즉 매소드의 재활용성을 높이기 위한 기술이라고 볼수 있다. 

 자주 사용되는 예제는,  면적을 함수 Area를 공통으로 정의한 인터페이스를 선언하고,  Circle, Square등 타입에서 인터페이스 함수를 구현하는 예제일것 같다. 다음과 같이 Area라는 함수의 인터페이스를 정의한다.

type Shape interface {
    Area() float64
}

다음과 같이 Area 메소드를, 즉 Shape 인터페이스를 구현하는 Circle과 Square를 정의한다.

type Square struct {
    X float64
 }
func (s Square) Area() float64 {
    return s.X * s.X
}
type Circle struct {
    R float64
}
func (c Circle) Area() float64 {
    return 2*c.R*math.Pi
}

어떤 도형의 면적을 출력해 주는 함수를 정의한다고 할 때, Square, Circle에 각각 출력하는 함수를 만들지 않고, Shape interface를 받아서 Shape의 Area함수를 이용하는 함수를 정의한다.

func PrintArea(shape Shape){
    fmt.Println("Area is:",shape.Area())
}

특정 도형의 면적을 출력할때 이 함수를 사용한다.

s:=Square{X:10.0}
c:=Circle{R:5.5}
PrintArea(s)
PrintArea(c)

 

 

* 타입 어써션(type assertion)

타입 어써션이란 x.(T) 형식으로 x는 인터페이스 타입 Value, T는 구체적 타입을 지정하여 사용하는데, 타입 어써션은 인터페이스로 사용되는 타입이 실제 어떤 타입인지를 확인하기 위한 방법이다. 타입 어써션으로 해당 인터페이스가 특정한 타입인지를 확인 가능하며, 해당 인터페이스의 실제 타입에 어떤 작업을 할 수 있도록 해주는 것이다. 위의 예제에서 Circle인 경우 Radius를 출력해주려고 하면 다음과 같이 PrintArea 함수를 만들 수 있다.

func PrintArea(shape Shape) {
    c, ok := shape.(Circle)
    if ok {
        fmt.Println("Radius:", c.R)
    }
    fmt.Println("Area is:", shape.Area())
}

 switch문에서 사용하려면 x.(type) 형태로 다음과 같이 사용된다.

func PrintArea(shape Shape) {
    switch v := shape.(type) {
    case Circle:
        fmt.Println("Radius:", v.R)
    case Square:
        fmt.Println("Side:", v.X)
    }
    fmt.Println("Area is:", shape.Area())
}

 

* 리플렉션(Reflection)

 리플렉션은 주어진 오브젝트에 대한 정보를 동적으로 알아내는 데 사용된다. fmt와 같이 지금은 없지만 향후 새로 정의될 데이터 타입을 다루는 코드를 작성하기 위해서 사용된다. 즉 최대한 범용성을 제공하기 위해서 사용된다. 주요 사용되는 타입은 reflect.Value와 reflect.Type이다.  다음은 reflect를 사용하는 예이다.

s:=Square{X:10.0}
c:=Circle{R:5.5}
//Type 조회
fmt.Println(reflect.TypeOf(s)) --> main.Square
refl:=reflect.ValueOf(&c).Elem()
//필드 개수 조회
fmt.Println("numOfField:",refl.NumField()) --> 1
//필드 값 조회
fmt.Println("field1:",refl.Field(0))
//메소드 수 조회
fmt.Println("numOfMethod:",refl.NumMethod()) --> 1

reft:=reflect.ValueOf(&c).Type()
//메소드 이름 조회
fmt.Println("methodName1:",reft.Method(0).Name)
//메소드 호출
fmt.Println("methodCall1:", reflect.ValueOf(&c).MethodByName("Area").Call([]reflect.Value{})[0]) --> 34.557519....

 reflect는 https://pkg.go.dev/reflect 에서 타입들을 확인 가능하다. 리플렉트는 강력한 기능이지만, 코드를 이해하고 관리하기가 힘들고, 실행 속도가 느릴 뿐만 아니라, 에러를 빌드 시간에 잡기 어렵다. 따라서 일반화가 꼭 필요한 코드에서만 사용해야 한다.

 

반응형
Posted by alias
,