Go 언어 interface – 업무에 사용하는 Go 언어 13

Go 언어 interface
업무에 사용하는 Go 언어 시리즈 13번째.

업무에 사용하는 Go 언어 썸네일

Go interface는 동작(Behavior)을 정의하는 추상 타입입니다.
인터페이스는 구조체나 다른 타입들이 특정 메서드를 구현했는지를 확인하여 다형성을 지원합니다.
이를 통해 Go는 객체 지향 프로그래밍의 중요한 요소 중 하나인 다형성(Polymorphism)을 효과적으로 제공합니다.

여러 타입이 있고 각 타입에 대한 동작의 의미는 같으나 코드는 분리해야 할 때 사용하면 좋다.
예를 들면, 사각형, 원 등 여러 도형에 대하여 넓이를 구한다는 동작의 의미는 동일하지만 도형 별로 코드는 분리되어야 한다.
각 타입에 대해 리시버를 지정한 메소드를 전부 정의해도 좋지만, 인터페이스를 두고 구현하여 코드를 재활용할 수 있도록 해보자.
그러면 같이 일하는 사람이 좋아할 것이다.

아래는 인터페이스에 대한 주요 내용과 예제 코드입니다.


Go 언어 인터페이스 기본 개념

  1. 인터페이스 정의
    • 인터페이스는 메서드 시그니처(메서드 이름, 매개변수, 반환 타입)를 정의합니다.
    • 인터페이스를 구현하기 위해 구조체는 정의된 메서드들을 구현하면 됩니다. 명시적으로 “implements” 키워드가 필요하지 않습니다.
  2. 빈 인터페이스 (Empty Interface)
    • interface{}는 어떤 타입도 저장할 수 있는 빈 인터페이스를 의미합니다.
    • 모든 타입은 interface{}를 구현합니다.
  3. 유형 단언(Type Assertion)
    • 인터페이스 타입에서 구체적인 타입으로 값을 변환하려면 유형 단언을 사용할 수 있습니다.
  4. 다형성 (Polymorphism)
    • 인터페이스를 사용하여 다양한 구조체를 공통적으로 처리할 수 있습니다.

예제 코드 1: 기본 인터페이스 사용법

Go
package main

import "fmt"

// 인터페이스 정의
type Shape interface {
    Area() float64
    Perimeter() float64
}

// 구조체 정의 및 메서드 구현
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * 3.14 * c.Radius
}

func main() {
    // Shape 인터페이스 사용
    var s Shape

    s = Rectangle{Width: 10, Height: 5}
    fmt.Printf("Rectangle Area: %.2f\n", s.Area())
    fmt.Printf("Rectangle Perimeter: %.2f\n", s.Perimeter())

    s = Circle{Radius: 7}
    fmt.Printf("Circle Area: %.2f\n", s.Area())
    fmt.Printf("Circle Perimeter: %.2f\n", s.Perimeter())
}

예제 코드 2: 빈 인터페이스와 다형성

Go
package main

import "fmt"

// 모든 타입을 받을 수 있는 빈 인터페이스
func PrintAnything(i interface{}) {
    fmt.Println(i)
}

func main() {
    PrintAnything(42)          // 정수
    PrintAnything("Hello Go")  // 문자열
    PrintAnything(3.14)        // 실수
}

empty interface의 경우 매우 활용도가 높습니다.
Go 언어의 유연함을 체감할 수 있는데, javascript처럼 어떠한 타입이 되었든 값을 취급할 수 있는 변수가 필요할 때 유용합니다.


예제 코드 3: 타입 선언(Type Assertion)

Go
package main

import "fmt"

func main() {
    var i interface{} = "Hello"

    // 타입 선언
    s, ok := i.(string)
    if ok {
        fmt.Printf("i is a string: %s\n", s)
    } else {
        fmt.Println("i is not a string")
    }

    // 타입 언 실패 예
    n, ok := i.(int)
    if !ok {
        fmt.Printf("Type assertion failed. i is not an int: %v\n", n)
    }
}

type assertion은 empty interface 변수의 값에 대한 타입을 확인하는 데에 활용할 수 있습니다.

필자의 업무 예시

API를 구성하여 마케팅과 관련된 특정 엔드포인트(POST method)에 값이 들어오면 DB에 저장하는 코드가 있습니다.
해당 API는 특정한 권한이 필요하지 않은 API이기 때문에 어뷰징(혹은 어노잉)이 발생하였습니다. ( 세상에는 이상한 사람이 너무 많습니다. )
따라서, 규칙으로 정한 외 타입의 값을 지속적으로 전달받았을 때 해당 IP를 차단 및 경고 메시지를 노출하는 기능을 개발했습니다.

이 때 empty interface와 type assertion을 활용했습니다.


예제 코드 4: 인터페이스로 다형성 구현

Go
package main

import "fmt"

// Animal 인터페이스 정의
type Animal interface {
    Speak() string
}

// Dog 구조체와 메서드 구현
type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

// Cat 구조체와 메서드 구현
type Cat struct{}

func (c Cat) Speak() string {
    return "Meow!"
}

// Main 함수
func main() {
    animals := []Animal{Dog{}, Cat{}}

    for _, animal := range animals {
        fmt.Println(animal.Speak())
    }
}

다음 글에서 알아볼 내용

Go 언어 error