개발일기

Go - 인터페이스 선언 및 사용법 본문

프로그래밍 언어/Go

Go - 인터페이스 선언 및 사용법

Flashback 2022. 8. 28. 12:35
728x90
반응형

인터페이스는 구조체와는 다르게 메서드들의 집합을 나타낸다. 인터페이스에서는 메서드 자체의 내부 로직을 구현하지는 않지만 해당 메서드를 호출하여 사용한다.

 

1. 인터페이스 정의

package main
import "fmt"

type IFruit interface {
    // 메서드 나열
    // setFruitInfo() int // 해당 메서드의 반환값이 존재할 경우
    setFruitInfo() // 반환값이 존재하지 않을 경우
}

/*
    type [인터페이스명] interface {
        메서드 나열...
    }
*/

func main() {
    
}

인터페이스를 선언할 때는, 위와 같이 자료형 부분에 interface라고 써주며 메서드들을 나열하면 된다. 반환값이 존재할 경우, 해당 메서드 뒤에 반환 자료형을 입력하면 된다.

 

2. 인터페이스 선언

package main
import "fmt"

type IFruit interface {
    // 메서드 나열
    setFruitInfo()
}

func main() {
    var fruit IFruit // 변수와 같은 형식으로 선언
    // fruit := IFruit
    fmt.Println(fruit)
}

/*
    실행결과
    <nil>
*/

인터페이스를 선언하고 연결된 메서드가 없는 경우, <nil>값이 출력된다.

 

3. 인터페이스 호출

인터페이스에 선언된 메서드를 호출하기 위해서는 인터페이스 메서드 부분에 기존 자료형을 재정의한 새로운 자료형을 대입하여야 사용할 수 있다.

package main
import "fmt"

// 기존 string 타입 재정의
type stringVal string
// type [새로운 타입명] [기존 타입명]

type IFruit interface {
    // 메서드 나열
    setFruitInfo()
}

// func (새로운 자료형) 메서드명() { ... }
func (s stringVal) setFruitInfo() {
    fmt.Println("fruit info test")
}

func main() {
    var fruit IFruit
    var fruitName stringVal = "mango"
}

새로운 자료형은 type 키워드 뒷부분에 새로운 타입명과 기존 타입명을 순서대로 입력하면 된다.

  • 기존 타입명 : string, int, float64 등으로 이루어진 자료형을 나타낸다. 
  • 새로운 타입명 : string, int, float64 등의 자료형을 지정할 때, 대체할 이름을 나타낸다.

새로운 자료형을 인터페이스 메서드에서 사용하려면 메서드명 앞 괄호에 새로운 자료형을 대입하여 메서드와 연결한다.

이로인해 fruit이라는 인터페이스 변수에 stringValue 자료형으로 정의된  mango라는 값을 대입할 수 있다.

 

만약 기존 자료형의 값을 인터페이스 변수에 대입하려면 다음과 같은 오류가 발생한다.

cannot define new methods on non-local type

 


 

새로운 자료형으로 인터페이스에 값을 대입하면 해당 인터페이스에 포함된 메서드들을 호출하여 사용할 수 있다.

func main() {
    var fruit IFruit
    var fruitName stringVal = "mango"
    fruit = fruitName
    fruit.setFruitInfo() // .(점)으로 연결하여 호출
}

/*
    실행 결과 : 
    fruit info test
*

IFruit 인터페이스에 포함되어 있는 메서드를 .(점)으로 연결하여 호출 할 수 있다.

 

3-1. 다른 자료형의 인터페이스 메서드 호출

동일한 이름을 가진 인터페이스 메서드가 존재한다면, 대입된 값의 자료형에 따라 메서드를 다르게 호출 할 수 있다.

package main
import "fmt"

// 기존 string 타입, int 타입 재정의
type stringVal string
type intVal int

type IFruit interface {
    // 메서드 나열
    setFruitInfo()
}

func (name stringVal) setFruitInfo() {
    fmt.Println("fruit name : ", name)
}

func (price intVal) setFruitInfo() {
    fmt.Println("fruit price : ", price)
}

func main() {
    var fruit IFruit
    var fruitName stringVal = "mango"
    var fruitPrice intVal = 1000
  
    fruit = fruitName
    fruit.setFruitInfo()

    fruit = fruitPrice
    fruit.setFruitInfo()
}

/*
    실행 결과 : 
    fruit name :  mango
    fruit price :  1000
*/

stringVal과 intVal로 string과 int타입의 자료형을 새로이 정의한 후, 각각의 자료형으로 setFruitInfo() 메서드를 연결하였다. main() 함수 부분에서 인터페이스 변수에 대입되는 값의 타입에 따라 호출되는 메서드가 바뀌게 된다.

stringVal 타입의 값이 대입된 경우에는 과일의 이름을 출력하는 메서드가 호출된다. intVal 타입의 값이 대입된 경우에는 과일의 가격을 출력하는 메서드가 호출된다.

 

4. 구조체를 활용한 인터페이스

package main

import "fmt"

type IFood interface {
    printFoodInfo()
}

type fruitStruct struct {
    name string
    price int
}

type juiceStruct struct {
    name string
    price int
}

func (f fruitStruct) printFoodInfo() {
    fmt.Println("과일 이름은 ", f.name, "입니다")
    fmt.Println("과일 가격은 ", f.price, "입니다")
}

func (j juiceStruct) printFoodInfo() {
    fmt.Println("주스 이름은 : ", j.name, "입니다")
    fmt.Println("주스 가격은 : ", j.price, "입니다")
}
func main() {
    fruit := fruitStruct{"mango", 1000}
    juice := juiceStruct{"orange", 3000}

    var food IFood
    food = fruit
    food.printFoodInfo() // 과일 정보 출력

    fmt.Println("\n")
  
    food = juice
    food.printFoodInfo() // 주스 정보 출력
}

/*
    실행 결과 : 
    과일 이름은  mango 입니다
    과일 가격은  1000 입니다

    주스 이름은 :  orange 입니다
    주스 가격은 :  3000 입니다
*/

 

  • 구조체 정의 : 과일과 주스에 관한 정보를 가지고 있는 구조체를 fruitStruct, juiceStruct로 정의한다.
  • 인터페이스 정의 : 음식에 관한 정보를 출력하는 인터페이스를 정의하고 메서드를 추가한다.
  • 인터페이스 메서드 : printFoodInfo라는 이름을 가진 메서드를 자료형을 달리한 채로 2개 정의한다. 인터페이스에 대입되는 값의 자료형에 따라 실행될 메서드가 바뀌게 된다.
  • fruitStruct 타입의 메서드 호출 : fruit이라는 인터페이스 변수에 fruitStruct라는 구조체 타입을 가진 구조체 변수를 대입하였다. 해당 인터페이스와 연결된 printFoodInfo() 메서드를 호출하면 fruitStruct 타입의 메서드인 func (f fruitStruct) printFoodInfo() { ... } 메서드가 호된다.
  • juiceStruct 타입의 메서드 호출 : juice라는 인터페이스 변수에 juiceStruct라는 구조체 타입을 가진 구조체 변수를 대입하였다. 해당 인터페이스와 연결된 printFootInfo() 메서드를 호출하면 juiceStruct 타입의 메서드인 func (j juiceStruct) printFoodInfo() { ... } 메서드가 호출된다.

 

5. 공백 인터페이스

인터페이스에 메서드를 선언하지 않은 공백 상태의 인터페이스를 생성할 수 있다.

package main

import "fmt"

func main() {
    var i interface{}
    fmt.Println(i)
}

/*
    실행 결과 : 
    <nil>
*/

연결된 메서드가 존재하지 않기 때문에 <nil>값이 출력된다.

 

5-1. 모든 타입 호출

공백 인터페이스는 타입에 상관없이 모든 타입의 메서드들을 호출할 수 있다. 기존에 타입이 지정된 인터페이스와는 다르게 기본 자료형인 string, int, float64 등의 자료형도 타입으로 지정하여 호출 할 수 있다.

package main

import "fmt"

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

func main() {
    var i interface{}
    i = "오렌지" // string 타입의 값 대입
    desc(i)
}

/*
    실행 결과 : 
    오렌지
*/

위와 같이 공백 인터페이스에는 자료형의 제약없이 값이 대입되며 호출된 메서드에서 인터페이스의 값을 확인할 수 있다.

 

5-2. 타입별 구분

공백 인터페이스에는 모든 타입의 자료형이 들어갈 수 있지만 특정한 타입의 자료형이 들어올 경우 다른 로직을 실행해야 할 경우 switch를 통해 타입을 구분하여 실행 부분을 변경할 수 있다.

package main

import "fmt"

func desc(i interface{}) {
    // 들어오는 인터페이스 타입을 구별한다.
    switch i.(type) {
        case string:
          fmt.Println("string type : ", i)
        case int:
          fmt.Println("int type : ", i)
    } // switch / case로 타입별 실행할 부분을 변경한다.
}

func main() {
    var i interface{}
    i = "오렌지"
    desc(i) // string type

    i = 1000
    desc(i) // int type
}

/*
    실행 결과 : 
    string type :  오렌지
    int type :  1000
*/

switch의 조건 부분에 [인터페이스 변수].(type) 형식의 i.(type)을 통해 들어온 인터페이스 값을 구별 할 수 있다. switch / case로 타입을 구분한 후, 해당하는 타입일 때 실행할 출력 부분 및 함수 호출 등을 달리할 수 있다.

 

만약 switch / case 구문 밖에서 i.(type)를 사용하면 다음과 같은 오류가 발생한다.

use of .(type) outside type switch

 

5-2. if문으로 타입 구별

package main

import "fmt"

func desc(i interface{}) {
    if v, ok := i.(string); ok {
        fmt.Println("타입은 string입니다.", v, ok)
    } else if v, ok := i.(int); ok {
        fmt.Println("타입은 int입니다", v, ok)
    }
    // v에는 인터페이스에 대입된 값
    // ok에는 해당 타입과 일치하는지의 여부가 boolean 타입- true /false로 대입된다
}

func main() {
    var i interface{}
    i = "오렌지"
    desc(i)

    i = 1000
    desc(i)
}

/*
    실행 결과 : 
    타입은 string입니다. 오렌지 true
    string type :  오렌지
    타입은 int입니다 1000 true
    int type :  1000
*/

switch / case 뿐만 아니라 if문에서도 타입을 구별하여 실행할 로직을 변경할 수 있다.

 


참고 사이트 : 

https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go

 

How to add new methods to an existing type in Go?

I want to add a convenience util method on to gorilla/mux Route and Router types: package util import( "net/http" "github.com/0xor1/gorillaseed/src/server/lib/mux" ) func (r *mux.Route)

stackoverflow.com

 

https://go.dev/tour/methods/14

 

A Tour of Go

 

go.dev

 

https://www.alexedwards.net/blog/interfaces-explained#:~:text=What%20is%20an%20interface%20in,some%20other%20type%20must%20have.&text=We%20say%20that%20something%20satisfies,exact%20signature%20String()%20string%20. 

 

Golang Interfaces Explained - Alex Edwards

Not sure how to structure your Go web application? My new book guides you through the start-to-finish build of a real world web application in Go — covering topics like how to structure your code, manage dependencies, create dynamic database-driven pages

www.alexedwards.net

 

https://medium.com/golangspec/interfaces-in-go-part-ii-d5057ffdb0a6

 

Interfaces in Go (part II)

Type assertion & type switch

medium.com

 

https://stackoverflow.com/questions/34820469/can-i-compare-variable-types-with-type-in-golang

 

Can I compare variable types with .(type) in Golang?

I'm quite confused about the .(type) syntax for interface variables. Is it possible to use it like this: var a,b interface{} // some code if first.(type) == second.(type) { } or is reflect.TypeOf...

stackoverflow.com

 

728x90
반응형
Comments