본문 바로가기

Language/Go

[기초] Go 기초 정리 - 5 (Interface)

Tour of Go를 기반으로한 Go의 기본 내용입니다.

Interface

  • Interface는 메소드의 시그니쳐 집합으로 정의된다.
  • 인터페이스 유형의 값은 해당 인터페이스의 메소드를 모두 구현하는 타입이라면 어떤 유형이든 가질 수 있음.
package main

import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f  // a MyFloat implements Abser
	a = &v // a *Vertex implements Abser

	// In the following line, v is a Vertex (not *Vertex)
	// and does NOT implement Abser.
	//a = v //Vertex포인터는 Abs가 구현되어있지만 Vertex는 그렇지 않으므로 오류!

	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

위 예제를 보게되면 Vertex struct 타입과 Myfloat 타입을 통해서 어떻게 interface구현이 되는지 확인 할 수 있다.

여기서 Myfloat는 Myfloat에 Abser인터페이스가 요구하는 Abs메서드를 구현하고 있고, Vertex는 Vertex포인터에서 Abs메서드를 구현하고 있음을 알 수 있다. 이때 주의해야할 것은 Vertex의 경우는, Vertex포인터에 Abs 메서드가 매핑이 되어있기 때문에 Vertex 타입은 Abser 인터페이스가 받을 수 없다는 점이다!

 

  • 인터페이스의 구현은 JAVA와 같이 implement하는 것과 같이 명시적으로 이루어지지 않음.(암시적 구현)
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

// 이 메서드는 type T가 interface I를 구현(implement)함을 의미하지만,
// 그것을 명시하지는 않는다.
func (t T) M() {
	fmt.Println(t.S)
}

func main() {
	var i I = T{"hello"}
	i.M()
}
  • 인터페이스의 값은 값과 그 값의 타입으로 이루어짐.
  • 이는 인터페이스로 메서드를 호출하면 기본 형식에 동일한 함수가 실행됨을 의미한다.
package main

import (
	"fmt"
	"math"
)

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	fmt.Println(t.S)
}

type F float64

func (f F) M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
  • Nil 인터페이스 구분
    • 인터페이스 자체에 할당된 값이 0일 경우, 그 메서드는 nil 리시버로 호출된다. 이것이 인터페이스가 nil을 의미 하지 않음.
    • 인터페이스에 아무 값도 할당되지 않았을 경우, 이 인터페이스는 nil 인터페이스이다.
package main

import "fmt"

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

func main() {
	var i I

	var t *T
	i = t
	describe(i)
	i.M()

	i = &T{"hello"}
	describe(i)
	i.M()
    
    // 아래 예시의 경우, I인터페이스 j자체가 nil이므로 에러 발생!
    //var j I
	//describe(j)
	//j.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}

 

  • 빈 인터페이스는 모든 유형의 값을 가질 수 있습니다.
  • 빈 인터페이스는 알 수 없는 값을 처리할 때 사용되고, 대표적인 예로 fmt의 Print입니다.
package main

import "fmt"

func main() {
	var i interface{}
	describe(i)

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) { //빈 인터페이스로 아무값이나 받을 수 있음.
	fmt.Printf("(%v, %T)\n", i, i)
}

 

Type assertion

  • type assertion은 인터페이스의 기존 값을 접근할 수 있도록 해준다.
t := i.(T)
  • 위 예시는 i는 type T의 값을 가지고 있다면, 그것을 변수 t에 type T로 할당한다는 뜻.
  • 만약 i가 T를 가지고 있지 않다면 panic 상태로 들어간다.
  • type assertiond를 테스트하고 싶다면 type assert의 성공여부를 판단하는 변수를 받아서 확인하면 된다.
t, ok := i.(T)
  • 만약 type assert가 통과 되었다면 ok는 true, t에는 type T로 값이 반환될 것이고, 만약 통과되지 않았다면 ok는 false, t에는 zero가 반환되며 별도로 panic은 발생하지 않는다.
  • Type switch는 타입에 따라 특정한 로직을 실행하고 싶을때 사용된다.
switch v := i.(type) {
case T:
    // v는 Type T를 가지고 있음.
case S:
    // v는 Type S를 가지고 있음.
default:
    // 매칭되는 값 없음.
}
  • case는 v의 타입과 비교되며 일치하는 부분의 로직이 실행됨.
package main

import "fmt"

func do(i interface{}) {
	
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
	
	var test interface{} = 2
	result, ok := test.(string)
	fmt.Println(result,ok)
}
/*result : 

Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!
 false

*/

 

**Stringer, Error등 Tour of Go에서 제공하는 예제로 실제 인터페이스를 구현해보는 연습도 해보시기 바랍니다!

 

Stringer 문제

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ipaddr IPAddr) String() string{
	return fmt.Sprintf("%d.%d.%d.%d",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
}

/* result : 

loopback: 127.0.0.1
googleDNS: 8.8.8.8

*/

 

Errors 문제

package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt)Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v",float64(e))
}

func Sqrt(x float64) (float64, error) {
	
	if x < 0 {
		return x, ErrNegativeSqrt(x)
	}
	z := float64(1)
	for i:=0 ; i < 10000; i++{
		z -= (z*z -x) / 2*z
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

/* result :

1.4106685607006662 <nil>
-2 cannot Sqrt negative number: -2

*/

 

Readers 문제

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte)(int, error){
	for i := range b {
		b[i] = 'A'
	}
	
	return len(b), nil
}
func main() {
	reader.Validate(MyReader{})
}

/* result :

OK!

*/

 

rot13Reader 문제

package main

import (
	"io"
	"os"
	"strings"
	"errors"
)

type rot13Reader struct {
	r io.Reader
}
func (rot *rot13Reader)Read(buf []byte) (n int, err error){
	
	n, err = rot.r.Read(buf)
	for i, v := range buf {
		_rot13 := v+13
		if (_rot13 >= 'a' && _rot13 <= 'z') || (_rot13 >= 'A' && _rot13 <= 'Z'){
			buf[i] = _rot13
		}else{
			buf[i] = _rot13 - 26
		}
	}
	if err != nil {
		err = errors.New("EOF")
		return
	}
	
	return
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

/* result :

You cracked the code 

*/

 

....