구조체(struct)가 필드들의 집합체라면, interface는 메서드들의 집합체입니다. interface는 타입(type)이 구현해야 하는 메서드 원형(prototype)들을 정의합니다. 하나의 사용자 정의 타입이 interface를 구현하기 위해서는 단순히 그 인터페이스가 갖는 모든 메서드들을 구현하면 됩니다. 인터페이스의 이름은 보통 변수에 ~er이 붙습니다. ex) printer interface {}

인터페이스는 struct와 마찬가지로 type 문을 사용하여 정의합니다.

type Shape interface {
    area() float64
    perimeter() float64
}

구현

예제 1

인터페이스를 구현하기 위해서는 해당 타입이 그 인터페이스의 메서드들을 모두 구현하면 되므로, 위의 Shape 인터페이스를 구현하기 위해서는 area(), perimeter() 2개의 메서드만 구현하면 됩니다. 예를 들어 Rect와 Circle이라는 2개의 타입이 있을 때, Shape 인터페이스를 구현하기 위해서는 아래와 같이 각 타입별로 2개의 메서드를 구현해 주면 됩니다.

//Rect 정의
type Rect struct {
    width, height float64
}
 
//Circle 정의
type Circle struct {
    radius float64
}
 
//Rect 타입에 대한 Shape 인터페이스 구현 
func (r Rect) area() float64 { return r.width * r.height }
func (r Rect) perimeter() float64 {
     return 2 * (r.width + r.height)
}
 
//Circle 타입에 대한 Shape 인터페이스 구현 
func (c Circle) area() float64 { 
    return math.Pi * c.radius * c.radius
}
func (c Circle) perimeter() float64 { 
    return 2 * math.Pi * c.radius

예제 2

package main

import "fmt"

type MyInt int // int 형을 MyInt로 정의

func (i MyInt) Print() { // MyInt에 Print 메서드를 연결
	fmt.Println(i)
}

type Rectangle struct { // 사각형 구조체 정의
	width, height int
}

func (r Rectangle) Print() { // Rectangle에 Print 메서드를 연결
	fmt.Println(r.width, r.height)
}

type Printer interface { // Print 메서드를 가지는 인터페이스 정의
	Print()
}

func main() {
	var i MyInt = 5
	r := Rectangle{10, 20}

	var p Printer // 인터페이스 선언

	p = i     // i를 인터페이스 p에 대입
	p.Print() // 5: 인터페이스 p를 통하여 MyInt의 Print 메서드 호출

	p = r     // r을 인터페이스 p에 대입
	p.Print() // 10 20: 인터페이스 p를 통하여 Rectangle의 Print 메서드 호출
}

MyInt 자료형, Ractangle 구조체, Printer 인터페이스를 그림으로 표현하면 다음과 같습니다. MyInt를 정의하고, 너비와 높이를 가지는 사각형 구조체 Rectangle을 정의했습니다. 그리고 MyInt, Rectangle 모두 자신의 내용을 출력하는 Print 함수를 구현했습니다. 이제 두 타입 모두 똑같은 Print 함수를 가지고 있습니다(여기서 똑같은 함수라는 것은 함수 이름, 매개변수 자료형, 리턴값 자료형이 모두 같은 상태를 뜻합니다).


사용

인터페이스를 사용하는 일반적인 예로 함수가 파라미터로 인터페이스를 받아들이는 경우를 들 수 있습니다. 함수 파라미터가 interface인 경우, 이는 어떤 타입이든 해당 인터페이스를 구현하기만 하면 모두 입력 파라미터로 사용될 수 있다는 것을 의미합니다.

아래 예제에서 showArea() 함수는 Shape 인터페이스들을 파라미터로 받아들이고 있는데, 따라서 Rect와 Circle 처럼 Shape 인터페이스를 구현한 타입 객체들을 파라미터로 받을 수 있습니다. showArea() 함수 내에서 해당 인터페이스가 가진 메서드 즉 area() 혹은 perimeter()을 사용할 수 있습니다.

func main() { r := Rect{10., 20.} c := Circle{10} showArea(r, c) } func showArea(shapes ...Shape) { for _, s := range shapes { a := s.area() //인터페이스 메서드 호출 println(a) } }

덕 타이핑

이렇게 각 값이나 인스턴스의 실제 타입은 상관하지 않고 구현된 메서드로만 타입을 판단하는 방식을 덕 타이핑(Duck typing)이라 합니다. 이 용어는 다음과 같은 덕 테스트(오리 테스트)에서 유래되었습니다. “만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라 부르겠다.” Go 언어로는 다음과 같이 덕 타이핑을 구현할 수 있습니다.

package main

import "fmt"

type Duck struct { // 오리(Duck) 구조체 정의
}

func (d Duck) quack() {     // 오리의 quack 메서드 정의
	fmt.Println("꽥~!") // 오리 울음 소리
}

func (d Duck) feathers() { // 오리의 feathers 메서드 정의
	fmt.Println("오리는 흰색과 회색 털을 가지고 있습니다.")
}

type Person struct { // 사람(Person) 구조체 정의
}

func (p Person) quack() {                           // 사람의 quack 메서드 정의
	fmt.Println("사람은 오리를 흉내냅니다. 꽥~!") // 사람이 오리 소리를 흉내냄
}

func (p Person) feathers() { // 사람의 feathers 메서드 정의
	fmt.Println("사람은 땅에서 깃털을 주워서 보여줍니다.")
}

type Quacker interface { // quack, feathers 메서드를 가지는 Quacker 인터페이스 정의
	quack()
	feathers()
}

func inTheForest(q Quacker) {
	q.quack()    // Quacker 인터페이스로 quack 메서드 호출
	q.feathers() // Quacker 인터페이스로 feathers 메서드 호출
}

func main() {
	var donald Duck // 오리 인스턴스 생성
	var john Person // 사람 인스턴스 생성

	inTheForest(donald) // 인터페이스를 통하여 오리의 quack, feather 메서드 호출
	inTheForest(john)   // 인터페이스를 통하여 사람의 quack, feather 메서드 호출
}


타입이 특정 인터페이스를 구현하는지 검사하려면 다음과 같이 사용합니다.

  • interface{}(인스턴스).(인터페이스)
var donald Duck

if v, ok := interface{}(donald).(Quacker); ok {
	fmt.Println(v, ok)
}
 
# 결과
# {} true

Duck 타입의 인스턴스 donald를 빈 인터페이스에 넣은 뒤 Quacker 인터페이스와 같은지 확인합니다. 첫 번째 리턴값은 검사했던 인스턴스이며 두 번째 리턴값은 인스턴스가 해당 인터페이스를 구현하고 있는지 여부입니다. 구현하고 있다면 true 그렇지 않으면 false입니다.

타입

Go 프로그래밍을 하다보면 흔히 빈 인터페이스(empty interface)를 자주 접하게 되는데, 흔히 인터페이스 타입(interface type)으로도 불립니다. 예를 들어, 여러 표준 패키지들의 함수 Prototype을 살펴보면, 아래와 같이 빈 interface가 자주 등장함을 볼 수 있습니다. 빈 interface는 interface{} 와 같이 표현합니다.

func Marshal(v interface{}) ([]byte, error); func Println(a ...interface{}) (n int, err error);


Empty interface는 메서드를 전혀 갖지 않는 빈 인터페이스로서, Go의 모든 Type은 적어도 0개의 메서드를 구현하므로, 흔히 Go에서 모든 Type을 나타내기 위해 빈 인터페이스를 사용합니다. 즉, 빈 인터페이스는 어떠한 타입도 담을 수 있는 컨테이너라고 볼 수 있으며, 여러 다른 언어에서 흔히 일컫는 Dynamic Type 이라고 볼 수 있습니다. (주: empty interface는 C#, Java 에서 object라 볼 수 있으며, C/C++ 에서는 void* 와 같다고 볼 수 있음)

아래 예제에서 인터페이스 타입 x는 정수 1을 담았다가 다시 문자열 Tom을 담고 있는데, 실행 결과는 마지막에 담은 Tom을 출력합니다.

package main import "fmt" func main() { var x interface{} x = 1 x = "Tom" printIt(x) } func printIt(v interface{}) { fmt.Println(v) //Tom }

Type Assertion

Interface type의 x와 타입 T에 대하여 x.(T)로 표현했을 때, 이는 x가 nil이 아니며, x는 T 타입에 속한다는 점을 확인(assert)하는 것으로 이러한 표현을 "Type Assertion"이라 부릅니다. 만약 x가 nil 이거나 x의 타입이 T가 아니라면, 런타임 에러가 발생할 것이고, x가 T 타입인 경우는 T 타입의 x를 리턴합니다. 즉, 아래 예제에서 변수 j는 a.(int)로부터 int형 변수 j가 됩니다.

func main() { var a interface{} = 1 i := a // a와 i 는 dynamic type, 값은 1 j := a.(int) // j는 int 타입, 값은 1 println(i) // 포인터주소 출력 println(j) // 1 출력 }


'언어 > GO' 카테고리의 다른 글

[GO] 채널  (0) 2017.03.02
[GO] 고루틴  (0) 2017.02.22
[GO] 인터페이스 (Interface)  (0) 2017.02.20
[GO] 파이썬 모듈 실행하기  (0) 2017.02.18
[GO] AES ECB 모드 암호화 (PKCS5, PKCS7)  (0) 2017.02.17
[GO] JSON 사용하기  (0) 2017.02.16

+ Random Posts