본문 바로가기
Programming/Golang

[golang] interface와 reflect, 그리고 OOP

by 라바킴 2021. 3. 28.

golang에는 type 문이 있다.

속성들의 집합을 표현하는 struct와 행위의 집합을 표현하는 interface를 정의할 때 주로 쓰인다.

struct는 Custom Data Type을 정의하며 interface는 해당 type이 정의해야하는 함수 원형을 정의한다.

 

#interface

golang에는 전통적인 class, object, 상속 개념이 정의되어 있지 않은 대신 struct와 interface라는 기능으로 OOP를 지원한다.

기존 class는 사용자 정의 타입을 정의하는 struct로 표현되는데, 속성(==field)과 행위(==method)를 함께 표현하지 않는다.

struct는 필드만을 가지게, method는 함수 이름 '앞'에 선언되는 매개 변수인 receiver 인수를 받는 func 로 선언된다.

 

type Rectangle struct {
    width, height int
}

func (r *Rectangle) Area() int {
    return r.width * r.height
}

 

여기서 선언된 area() 함수는 (r *Rectangle) 이라는 type 매개 변수로 인해 type method 로 분류되는 것이며 이는 method receiver 역할을 한다. method receiver는 this나 self 특정 키워드 지정을 할 필요없이 변수 이름처럼 정의하며 보통 type 변수 맨 앞글자를 따서 한 문자로 표현한다. 

 

이런 형태로 정의되는 type method는 interface와도 밀접한 관련이 있다. interface가 이 method의 집합을 나열한 형태로 구현되기 때문이다. 어떤 type이 특정한 interface를 따르기 위해선 여기 선언된 모든 method를 구현하여야 한다.

 

package shape_I

type Shape interface {
	Area() int
    	Perimeter float64
}

 

즉 interface는 추상 타입(abstract type) 으로 정의할 type이 어떤 interface의 instance가 되려고 할 때 반드시 구현해야 할 함수를 정의한다. pure virtual function의 집합처럼 말이다.

 

type rectangle struct {
	W, H float64
}

type circle struct {
	R float64
}

// ------------------------------------------------------------
func (r rectangle) Area() float64 {
	return r.W * r.H
}

func (r rectangle) Perimeter() float64 {
	return 2 * (r.H + r.W)
}

// ------------------------------------------------------------
func (c circle) Area() float64 {
	return c.R * c.R * math.Pi
}

func (c circle) Perimeter() float64 {
	return 2 * c.R * math.Pi
}

// ------------------------------------------------------------
func Calc(x shape_I.Shape) {

	switch x.(type) {
	case rectangle:
		fmt.Println("I'm a rectangle")
	case circle:
		fmt.Println("I'm a circle")
	}

	fmt.Println(x.Area())
	fmt.Println(x.Perimeter())

}

// ------------------------------------------------------------

func main() {
	x := rectangle{1.2, 4.8}
	Calc(x)
	y := circle{9}
	Calc(y)
}

 

#reflect

reflect는 구조체에 대한 정보를 동적으로 알아내는데 사용되는 golang의 고급 기능 중 하나이다.

대표적인 예로 우리에게 익숙한 fmt 패키지에서는 reflect를 확용해 모든 데이터 타입을 일일히 지정해 넣지 않아도 interface를 알아서 확인하고 해당 타입에 맞게 처리해준다.

 

reflect.Value 는 어떤 type이 가진 '값'을 저장할 때,

reflect.Type은 golang에서 지원하는 type을 표현하는데 사용된다.

 

func (p *pp) printArg(arg interface{}, verb rune) {
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString)
		default:
			p.badVerb(verb)
		}
		return
	}

	// Special processing considerations.
	// %T (the value's type) and %p (its address) are special; we always do them first.
	switch verb {
	case 'T':
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
	case 'p':
		p.fmtPointer(reflect.ValueOf(arg), 'p')
		return
	}

	// Some types can be done without reflection.
	switch f := arg.(type) {
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
		p.fmtFloat(f, 64, verb)
	case complex64:
		p.fmtComplex(complex128(f), 64, verb)
	case complex128:
		p.fmtComplex(f, 128, verb)
	case int:
		p.fmtInteger(uint64(f), signed, verb)
	case int8:
		p.fmtInteger(uint64(f), signed, verb)
	case int16:
		p.fmtInteger(uint64(f), signed, verb)
	case int32:
		p.fmtInteger(uint64(f), signed, verb)
	case int64:
		p.fmtInteger(uint64(f), signed, verb)
	case uint:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint8:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint16:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint32:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint64:
		p.fmtInteger(f, unsigned, verb)
	case uintptr:
		p.fmtInteger(uint64(f), unsigned, verb)
	case string:
		p.fmtString(f, verb)
	case []byte:
		p.fmtBytes(f, verb, "[]byte")
	case reflect.Value:
		// Handle extractable values with special methods
		// since printValue does not handle them at depth 0.
		if f.IsValid() && f.CanInterface() {
			p.arg = f.Interface()
			if p.handleMethods(verb) {
				return
			}
		}
		p.printValue(f, verb, 0)
	default:
		// If the type is not simple, it might have methods.
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
	}
}

 

reflect는 interface처럼 자주 사용되는 기능은 아니지만 향후 새로운 type이 추가되는 등, 처리 대상 type을 예측할 수 없는 상황에서 매우 유용하다. reflect의 기본 사용법은 위 fmt 패키지 함수로 대체한다.

 

 

ps)

struct+interface를 통해 어느정도 추상화된 go code를 template화 할 때 이 reflect를 활용하면 꽤나 정돈된 코드가 나올 수 있을 것이다.

어떻게 쓰냐에 따라 다양한 그림을 그릴 수 있는 기능인 것 같으니 '잘' 쓰는 방법을 연구해봐야 할 것 같다. 테스트 일지를 업데이트 해보도록 하자.

'Programming > Golang' 카테고리의 다른 글

go map 초기화  (0) 2020.10.18
[golang/err] import cycle not allowed  (0) 2019.04.06

댓글