타입 메소드
타입 메소드란 특수한 수신자 인수를 받는 함수다. 매개변수는 함수와 여기에 추가한 매개변수의 타입을 연결한다. 이러한 매개변수를 메소드의 수신자라 부른다.
func (f *File) Close() error {
if err := f.checkValid("close"); err != nil {
return err
}
return f.file.close()
}
Close()는 타입 메소드다. f 매개변수가 메소드의 수신자다. 수신자의 작동 과정을 oop용어로 표현하면 오브젝트에 메세지를 보냈다고 말한다.
package main
import "fmt"
type twoInts struct {
X int64
Y int64
}
func regularFunction(a, b twoInts) twoInts {
temp := twoInts{X : a.X + b.X, Y : a.Y + b.Y}
return temp
}
func (a twoInts) method(b twoInts) twoInts {
temp := twoInts{X : a.X + b.X, Y : a.Y + b.Y}
return temp
}
func main() {
i := twoInts{
X: 1,
Y: 2,
}
j := twoInts{
X: 3,
Y: 4,
}
fmt.Println(regularFunction(i,j))
fmt.Println(i.method(j))
}
$ go run foo.go
{4 6}
{4 6}
Go 인터페이스
Go에서 interface를 정의할 때 구체적인 동작을 구현할 메소드의 집합을 나열하는 방식으로 표현한다. 어떤 타입이 특정한 인터페이스를 따르기 위해서는, 그 인터페이스에서 정의한 모든 메소드를 구현해야 한다. 간단히 말해 인터페이스란 추상 타입으로 정의할 타입이 어떤 인터페이스의 인스턴스가 되기 위해 반드시 구현해야할 함수들을 정의한 것이다.
인터페이스의 가장 큰 장점은, 함수의 매개변수 타입을 인터페이스로 정의했을 때 그 인터페이스를 구현한 변수라면 어떤 것도 그 함수에 전달할 수 있다는 점이다.
타입 어써션
타입 어써션이란 x.(T) 형식의 표기법으로, x는 인터페이스 타입을, T는 구체적인 타입을 지정한다. 이때 x에 실제로 저장되는 값의 타입은 T이며, T는 반드시 x의 인터페이스를 충족해야한다.
타입 어써션으로 첫번째 인터페이스 값이 특정한 타입을 따르는지 확인할 수 있다. 타입 어써션은 값과 bool 타입의 값을 리넡한다.
두번째로 인터페이스에 저장된 구체적인 값을 이용하거나 이 값을 새 변수에 할당하는 것이다.
package main
import "fmt"
func main() {
var myInt interface{} = 123
k, ok := myInt.(int)
if ok {
fmt.Println("Success:", k)
}
v, ok := myInt.(float64)
if ok {
fmt.Println("Success:", v)
} else {
fmt.Println("failed")
}
}
$ go run foo.go
Success: 123
failed
인터페이스 직접 정의하기
package main
import "fmt"
type square struct {
X float64
}
type circle struct {
R float64
}
type rectangle struct {
X float64
Y float64
}
func tellInterface(x interface{}) {
switch v := x.(type) {
case square:
fmt.Println("square")
case circle:
fmt.Println("circle")
case rectangle:
fmt.Println("rectangle")
default:
fmt.Println("no...", v)
}
}
func main() {
x := circle{R: 10}
tellInterface(x)
y := rectangle{
X: 1,
Y: 2,
}
tellInterface(y)
}
go run foo.go
circle
rectangle
리플렉션
리플렉섹으로 주어진 오브젝트에 대한 타입 뿐만 아니라 구조체에 대한 정보를 동적으로 알아내는 데 사용된다.
fmt 패키지에서 리플렉션을 이용하면 모든 데이터 타입을 구체적으로 지정하지 않아도 된다. 설사 모든 타입에 일일이 대응하도록 코드를 작성해도, 코드에 입력될 있는 모든 종류의 타입을 예상한다는 것은 불가능하다.
결론적으로는 모르는 타입이나 그 값을 다룰 때 리플렉션을 활용하면 편하다.
package main
import (
"fmt"
"os"
"reflect"
)
type a struct {
X int
Y float64
Z string
}
type b struct {
F int
G int
H string
I float64
}
func main() {
x := 100
xRefl := reflect.ValueOf(&x).Elem()
xType := xRefl.Type()
fmt.Println("The type of x is", xType)
A := a{
X: 100,
Y: 200.12,
Z: "struct a",
}
B := b{
F: 1,
G: 2,
H: "struct b",
I: -1.2,
}
var r reflect.Value
arguments := os.Args
if len(arguments) == 1 {
r = reflect.ValueOf(&A).Elem()
} else {
r = reflect.ValueOf(&B).Elem()
}
iType := r.Type()
fmt.Printf("i Type : %s\n", iType)
fmt.Printf("he %d fields of %s are :\n", r.NumField(), iType)
for i := 0; i < r.NumField(); i++ {
fmt.Printf("Field name : %s ", iType.Field(i).Name)
fmt.Printf("with type: %s ", r.Field(i).Type())
fmt.Printf("and value %v\n", r.Field(i).Interface())
}
}
$ go run foo.go 1
The type of x is int
i Type : main.b
he 4 fields of main.b are :
Field name : F with type: int and value 1
Field name : G with type: int and value 2
Field name : H with type: string and value struct b
Field name : I with type: float64 and value -1.2
$ go run foo.go
The type of x is int
i Type : main.a
he 3 fields of main.a are :
Field name : X with type: int and value 100
Field name : Y with type: float64 and value 200.12
Field name : Z with type: string and value struct a
x 라는 변수를 선언 후, reflect.ValueOf(&x).Elem() 함수를 호출 후 xRefl.Type()을 호출하여 변수의 타입을 알아낸다.
NumField() 메소드는 reflect.Value 구조체에 있는 필드의 개수를 리턴한다. Interface()함수는 구조체에 있는 필드의 값을 인터페이스로 리턴한다.
More reflection
/code
package main
import (
"fmt"
"os"
"reflect"
)
type t1 int
type t2 int
type a struct {
X int
Y float64
Text string
}
func (a1 a) compareStruct(a2 a) bool {
r1 := reflect.ValueOf(&a1).Elem()
r2 := reflect.ValueOf(&a2).Elem()
for i := 0; i < r1.NumField(); i++ {
if r1.Field(i).Interface() != r2.Field(i).Interface() {
return false
}
}
return true
}
func printMethods(i interface{}) {
r := reflect.ValueOf(i)
t := r.Type()
fmt.Printf("Type to examine : %s\n", t)
for j := 0; j < r.NumMethod(); j++ {
m := r.Method(j).Type()
fmt.Println(t.Method(j).Name, "--->", m)
}
}
func main() {
x1 := t1(100)
x2 := t2(100)
fmt.Printf("The type of x1 is %s\n", reflect.TypeOf(x1))
fmt.Printf("The type of x2 is %s\n", reflect.TypeOf(x2))
var p struct {}
r := reflect.New(reflect.ValueOf(&p).Type()).Elem()
fmt.Printf("The type of r is %s\n", reflect.TypeOf(r))
a1 := a{
X: 1,
Y: 2.1,
Text: "A1",
}
a2 := a{
X: 1,
Y: -2,
Text: "A2",
}
if a1.compareStruct(a1) {
fmt.Println("Equal")
}
if !a1.compareStruct(a2) {
fmt.Println("Not Equal")
}
var f *os.File
printMethods(f)
}
$ go run foo.go
The type of x1 is main.t1
The type of x2 is main.t2
The type of r is reflect.Value
Equal
Not Equal
Type to examine : *os.File
Chdir ---> func() error
Chmod ---> func(os.FileMode) error
Chown ---> func(int, int) error
Close ---> func() error
Fd ---> func() uintptr
Name ---> func() string
Read ---> func([]uint8) (int, error)
ReadAt ---> func([]uint8, int64) (int, error)
Readdir ---> func(int) ([]os.FileInfo, error)
Readdirnames ---> func(int) ([]string, error)
Seek ---> func(int64, int) (int64, error)
SetDeadline ---> func(time.Time) error
SetReadDeadline ---> func(time.Time) error
SetWriteDeadline ---> func(time.Time) error
Stat ---> func() (os.FileInfo, error)
Sync ---> func() error
SyscallConn ---> func() (syscall.RawConn, error)
Truncate ---> func(int64) error
Write ---> func([]uint8) (int, error)
WriteAt ---> func([]uint8, int64) (int, error)
WriteString ---> func(string) (int, error)
r 타입은 reflect.Value라는 것을 알수 있다.
리플렉션의 세가지 단점
- 리플렉션을 너무 많이 사용하면 코드를 이해하고 관리하기 힘들어진다.
- 실행 속도가 느려진다. 구체적인 데이터 타입을 다루도록 작성된 코드가 리플렉션으로 데이터 타입을 동적으로 다루는 코드보다 훨씬 빠르다
- 리플렉션에 관련된 에러는 빌드 시간에 잡을 수 없고 런타임에 발견된다.
OOP in GO
Go는 상속을 지원하지 않는다. 대신 합성을 지원하고 다형성을 지원한다.
package main
import "fmt"
type a struct {
XX int
YY int
}
type b struct {
AA string
XX int
}
type c struct {
A a
B b
}
func (A a) A() {
fmt.Println("Function A() for A")
}
func (B b) B() {
fmt.Println("Function A() for B")
}
func main() {
var i c
i.A.A()
i.B.B()
}
$ go run foo.go
Function A() for A
Function A() for B
package main
import "fmt"
type first struct{}
func (a first) F() {
a.shared()
}
func (a first) shared() {
fmt.Println("This is shared() from first!")
}
type second struct {
first
}
func (a second) shared() {
fmt.Println("This is shared from second!")
}
func main() {
first{}.F()
second{}.shared()
i := second{}
j := i.first
j.F()
}
$ go run foo.go
This is shared() from first!
This is shared from second!
This is shared() from first!
'Go 언어 공부' 카테고리의 다른 글
[GO 마스터하기] 09-동시성 1 (0) | 2020.09.21 |
---|---|
[GO 마스터하기] 08-유닉스 시스템콜 (0) | 2020.09.21 |
[GO 마스터하기] 06-Go Package (0) | 2020.09.21 |
[GO 마스터하기] 05-Go 자료구조 (0) | 2020.09.16 |
[GO 마스터하기] 04-합성 타입 사용법 (0) | 2020.09.07 |