인터페이스(interface)

최승훈·2020년 8월 13일
0

Golang기초

목록 보기
5/7
post-thumbnail

메소드의 집합 인터페이스

구조체는 같은 속성의 필드의 집합체이고, 메소드는 함수 중에서도 구조체의 속성을 기능적으로 수행하는 특별한 함수.

인터페이스를 사용하지 않은 예제

예를들어 구조체로
원의 정보
사각형의 정보
가 있고 이 구조체를 이용해 넓이를 구하는 메소드가있다고 생각해보자,
기능은 같지만(넓이를 구한다) 두 구조체의 필드가 다르고 연산 방법도 다르기 때문에 메소드도 각각의 구조체에 선언해야함. 따라서 이름은 같지만(getArea)내용물이 다른 메소드를 만들어야 함.

package main

import (
	"fmt"
	"math"
)

type Rect struct {
	width, height float64
}

// Rect 의 넓이를 구하는 getArea 메소드
func (r Rect) getArea() float64 {
	return r.width * r.height
}

type Circle struct {
	radius float64
}

// Circle 의 넓이를 구하는 getArea 메소드
func (c Circle) getArea() float64 {
	return math.Pi * c.radius * c.radius
}

func main() {

	r1 := Rect{10,20}
	c1 := Circle{10}

	fmt.Println(r1.getArea())
	fmt.Println(c1.getArea())
}

구조체 정보를 이용해 넓이를 구하는 getArea()라는 메소드가 두개 있음.
두 개의 getArea()는 공통적으로 넓이를 구하는 기능을 하지만, 전달받는 구조체 객체와 그에 따른 연산 과정이 다르기 때문에 각각 따로 선언.
(전달받는 구조체가 다르기 때문에 메소드의 이름이 동일해도 상관없음.)

인터페이스를 사용한 예제.

많은 수의 객체의 넓이를 출력하려면?
전달하는 객체의 형이 다르기 때문에 같은 기능의 메소드이기 때문에, 매개변수를 다르게 설정해줘야함.
결론, 같은 속성의 기능을 묶어놓은 인터페이스를 활용하면 인터페이스에 있는 메소드들을 호출 할 수 있음.

package main

import (
	"fmt"
	"math"
)

// geometry 인터페이스 선언, Rect 와 Circle 구조체의 getArea 메소드를 모두 포함.
type geometry interface {
	getArea() float64
}

type Rect struct {
	width, height float64
}

// Rect 의 넓이를 구하는 getArea 메소드
func (r Rect) getArea() float64 {
	return r.width * r.height
}

type Circle struct {
	radius float64
}

// Circle 의 넓이를 구하는 getArea 메소드
func (c Circle) getArea() float64 {
	return math.Pi * c.radius * c.radius
}

func main() {

	r1 := Rect{10,20}
	c1 := Circle{10}

	fmt.Println(r1.getArea())
	fmt.Println(c1.getArea())

	printMeasure(r1, c1)
}

// 인터페이스를 가변인자로 받는 함수
func printMeasure(m ...geometry) {
	// 가변 인자 함수의 값은 슬라이스형
	for _ , m := range m {
		// 인터페이스의 메소드 호출
		fmt.Println(m.getArea())
	}
}

매개변수로 인터페이스를 사용한다는 것은, 구조체에 관계없이 인터페이스에 포함된 메소드를 사용하겠다는 뜻.

package main

import (
	"fmt"
	"math"
)

// geometry 인터페이스 선언, Rect 와 Circle 구조체의 getArea 메소드를 모두 포함.
type geometry interface {
	// 넓이
	getArea() float64
	// 둘
	getPerimeter() float64
}

type Rect struct {
	width, height float64
}

// Rect 의 넓이를 구하는 getArea 메소드
func (r Rect) getArea() float64 {
	return r.width * r.height
}

// Rect 의 둘레를 구하는 getPerimeter
func (r Rect) getPerimeter() float64 {
	return 2 * (r.width + r.height)
}

type Circle struct {
	radius float64
}

// Circle 의 넓이를 구하는 getArea 메소드
func (c Circle) getArea() float64 {
	return math.Pi * c.radius * c.radius
}

// Circle 의 둘레를 구하는 getPerimeter
func (c Circle) getPerimeter() float64 {
	return 2 * math.Pi * c.radius
}

func main() {

	r1 := Rect{10,20}
	c1 := Circle{10}

	fmt.Println(r1.getArea())
	fmt.Println(c1.getArea())

	printMeasure(r1, c1)
}

// 인터페이스를 가변인자로 받는 함수
func printMeasure(m ...geometry) {
	// 가변 인자 함수의 값은 슬라이스형
	for _ , m := range m {
		// 인터페이스의 메소드 호출
		fmt.Println(m)
		fmt.Println(m.getArea())
		fmt.Println(m.getPerimeter())
	}
}

Duck Typing

만약에 어떤 새가 오리처럼 걸어다니고, 헤엄치고, 꽥꽥 소리를 낸다면 나는 그 새를 오리라고 부르겠다.

=> 각 객체의 실제 타입은 상관하지 않고, 구현된 메소드로만 타입을 판단하는 것을 Duck Typing이라고 함!

package main

import "fmt"

// 나는 어떤 물체가 이륙(TakeOff)와 착륙(Landing)을 한다면 나는 그 물체를 비행기(AirPlane)이라고 부르겠다.
type AirPlane interface {
	TakeOff()
	Landing()
}

type A380 struct {
}

func (a A380) TakeOff() {
	fmt.Println("A380 이 이륙합니다.")
}

func (a A380) Landing() {
	fmt.Println("A380 이 착륙합니다.")
}

type B777 struct {
}

func (b B777) TakeOff() {
	fmt.Println("B777 이 이륙합니다.")
}

func (b B777) Landing() {
	fmt.Println("B777 이 착륙합니다.")
}

func inAirPort(a AirPlane) {
	a.TakeOff()
	a.Landing()
}

func main() {
	var KAL123 A380
	var AN222  A380
	var JN222  B777
	var HL222  B777

	inAirPort(KAL123)
	inAirPort(AN222)
	inAirPort(JN222)
	inAirPort(HL222)

}

빈 인터페이스(Empty Interface)

인터페이스도 type(형)으로 사용 할 수 있음.
1. 인터페이스는 내용을 따로 선언하지 않아도 형으로서 사용이 가능.
2. 인터페이스는 하나의 경이기 때문에 매개변수로 사용 가능.
3. 인터페이스는 어떠한 타입도 담을수 있는 컨테이너!(Dynamin type)

빈 인터페이스의 활용 예


package main

import "fmt"

func main() {

	var x interface{}

	x = 1
	println(x)

	x = "test"
	println(x)
}

func PrintValue(i interface{}) {
	fmt.Println(i)
}

Type Assertion

인터페이스형으로 선언된 변수는 초기화 하는 값에 따라 형이 자동 명시 되지만,
사실 타입(형)은 Danamic type임.
따라서 확실한 형을 표현하기 위해서는 'Type Assertion'을 할 필요가 있음.
인터페이스에서 형을 선언하기 위해서는 "변수이름.(type)"을 명시해주면 됨.

예제 1.

package main

import "fmt"

func main() {
	var num interface{} = 10
	a := num
	b := num.(int)

	fmt.Printf("%T, %d \n",a, a)
	printTest(b)
}

func printTest(i interface{}) {
	fmt.Printf("%T, %d\n", i, i)
}

예제 2.

package main

import (
	"fmt"
	"strconv"
)

type Human struct {
	name string
	age int
}

func main() {

	h1 := Human{
		name: "show",
		age: 30,
	}
	fmt.Println(formatString(h1))
	fmt.Println(formatString(&h1))
	fmt.Println(formatString("ss"))
	fmt.Println(formatString(1))
	fmt.Println(formatString(0.12))
}

// 빈 인터페이스 타입은 함수의 매개변수, 리턴값, 구조체의 필드로 사용할 수 있음.
func formatString(arg interface{}) string {
	// 빈 인터페이스를 사용하여 모든 타입을 받음
	// typeAssertion
	switch arg.(type) {
	case int:
		i := arg.(int)
		return strconv.Itoa(i)
	case float32:
		f := arg.(float32)
		return strconv.FormatFloat(float64(f), 'f', -1, 32)
	case float64:
		f := arg.(float64)
		return strconv.FormatFloat(f, 'f', -1, 64)
	case string:
		s := arg.(string)
		return s
	case Human:
		p := arg.(Human)
		return p.name + " " + strconv.Itoa(p.age)
	case *Human:
		p := arg.(*Human)
		return p.name + " " + strconv.Itoa(p.age)
	default:
		return "Error"
	}
}
profile
안녕하세요

0개의 댓글