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 |
댓글