-
[Golang] 인터페이스 (Interface)언어/Golang 2017. 2. 20. 20:19
구조체(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 출력 }