인터페이스 사용하기

이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07

빈 인터페이스 사용하기

인터페이스에 아무 메서드도 정의되어 있지 않으면 모든 타입을 저장할 수 있습니다.

func f1(arg interface{}) { // 모든 타입을 저장할 수 있음
}

함수의 매개변수를 arg interface{}처럼 지정하여 빈 인터페이이스를 받도록 하였습니다.

다음과 같이 빈 인터페이스는 Any로 표현할 수 있습니다.

type Any interface{} // 인터페이스에 메서드를 지정하지 않음

func f2(arg Any) {   // 모든 타입을 저장할 수 있음
}

빈 인터페이스 타입은 함수의 매개변수, 리턴값, 구조체의 필드로 사용할 수 있습니다.

이제 모든 타입을 받아서 내용을 출력하는 함수를 만들어보겠습니다.

package main

import (
	"fmt"
	"strconv"
)

//                      ↓ 빈 인터페이스를 사용하여 모든 타입을 받음
func formatString(arg interface{}) string {
	//       ↓ 인터페이스에 저장된 타입에 따라 case 실행
	switch arg.(type) {
	case int:                      // arg가 int형이라면
		i := arg.(int)         // int형으로 값을 가져옴
		return strconv.Itoa(i) // strconv.Itoa 함수를 사용하여 i를 문자열로 변환
	case float32:                  // arg가 float32형이라면
		f := arg.(float32)     // float32형으로 값을 가져옴
		return strconv.FormatFloat(float64(f), 'f', -1, 32)
                                       // strconv.FormatFloat 함수를 사용하여 f를 문자열로 변환
	case float64:                  // arg가 float64형이라면
		f := arg.(float64)     // float64형으로 값을 가져옴
		return strconv.FormatFloat(f, 'f', -1, 64)
                                       // strconv.FormatFloat 함수를 사용하여 f를 문자열로 변환
	case string:                   // arg가 string이라면
		s := arg.(string)      // string으로 값을 가져옴
		return s               // string이므로 그대로 리턴
	default:
		return "Error"
	}
}

func main() {
	fmt.Println(formatString(1))
	fmt.Println(formatString(2.5))
	fmt.Println(formatString("Hello, world!"))
}

실행 결과

1
2.5
Hello, world!

formatString 함수의 매개변수를 보면 타입을 interface{}로 지정하였습니다. 이렇게 하면 모든 타입을 처리할 수 있습니다. * 인터페이스에 저장된 타입을 알아내려면 switch 분기문 안에서 arg.(type)처럼 인터페이스 변수에 .(type)을 사용합니다. 단 이 방법은 switch 분기문 안에서만 사용할 수 있고, 일반적인 방법으로는 사용할 수 없습니다. * 타입에 따라 case로 나눕니다. * 빈 인터페이스 변수는 그대로 사용할 수 없으므로 arg.(int), arg.(float32), arg.(float64), arg.(string)처럼 타입을 지정하여 값을 가져옵니다. 이렇게 타입을 원하는 형태로 바꾸는 작업을 Type assertion이라고 합니다. * 각 타입에 맞게 strconv.Itoa, strconv.FormatFloat 함수를 사용하여 값을 문자열로 만듭니다. string의 값은 문자열이므로 변환하지 않고 그대로 리턴합니다.

일반 자료형뿐만 아니라 구조체 인스턴스 및 포인터도 빈 인터페이스로 넘길 수 있습니다.

package main

import (
	"fmt"
	"strconv"
)

type Person struct { // Person 구조체 정의
	name string
	age  int
}

func formatString(arg interface{}) string {
	switch arg.(type) {
	case Person:               // arg의 타입이 Person이라면
		p := arg.(Person)  // Person 타입으로 값을 가져옴
		return p.name + " " + strconv.Itoa(p.age) // 각 필드를 합쳐서 리턴
	case *Person:              // arg의 타입이 Person 포인터라면
		p := arg.(*Person) // *Person 타입으로 값을 가져옴
		return p.name + " " + strconv.Itoa(p.age) // 각 필드를 합쳐서 리턴
	default:
		return "Error"
	}
}

func main() {
	fmt.Println(formatString(Person{"Maria", 20}))
	fmt.Println(formatString(&Person{"John", 12}))

	var andrew = new(Person)
	andrew.name = "Andrew"
	andrew.age = 35

	fmt.Println(formatString(andrew))
}

실행 결과

Maria 20
John 12
Andrew 35

switch arg.(type) { }으로 인터페이스에 저장된 타입을 알아낸 뒤 각 구조체 타입별로 case를 만들어 처리합니다. 여기서 구조체를 그대로 넘겨줬다면 case Person:으로 처리하고, 구조체의 포인터를 넘겨줬다면 case *Person:으로 처리합니다. 마찬가지로 구조체일 때는 arg.(Person), 포인터일 때는 arg.(*Person)으로 값을 가져옵니다.

인터페이스에 저장된 타입이 특정 타입인지 검사하려면 다음과 같이 사용합니다.

var t interface{}
t = Person{"Maria", 20}

if v, ok := t.(Person); ok {
	fmt.Println(v, ok)
}

실행 결과

{Maria 20} true

인터페이스.(타입) 형식입니다. 첫 번째 리턴값은 해당 타입으로 된 값이며 두 번째 리턴값은 타입이 맞는지 여부입니다. 타입이 일치하면 true 그렇지 않으면 false입니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

01 June 2015