관리 메뉴

피터의 개발이야기

[GO] 인터페이스와 제네릭의 차이점은? 본문

Programming/GO

[GO] 인터페이스와 제네릭의 차이점은?

기록하는 백앤드개발자 2025. 3. 28. 00:24
반응형

ㅁ 들어가며

[GO] Tucker의 GO 언어 프로그래밍 - 목차

[GO] Tucker의 GO 언어 프로그래밍 - 24장 제네릭 프로그래밍을 작성하면서 제니릭과 인터페이스의 차이점에 대해서 공부하였다.

ㅇ 인터페이스와 제네릭은 프로그래밍에서 추상화와 재사용성을 높이는 데 사용되는 개념이지만, 그 목적과 사용 방식에 차이가 있다.

 

ㅁ 인터페이스

ㅇ 인터페이스는 객체의 행동(메서드 집합)을 정의하고, 이를 구현하는 타입에 따라 다형성을 제공한다.

package main

import "fmt"

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

// Circle 타입
type Circle struct {
	Radius float64
}

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

// Rectangle 타입
type Rectangle struct {
	Width, Height float64
}

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

func PrintArea(s Shape) {
	fmt.Printf("도형의 면적: %.2f\n", s.Area())
}

func main() {
	c := Circle{Radius: 5}
	r := Rectangle{Width: 10, Height: 4}

	PrintArea(c) // Circle 타입 사용
	PrintArea(r) // Rectangle 타입 사용
}

# 출력
도형의 면적: 78.50
도형의 면적: 40.00

 

ㅇ 메서드의 시그니처만 정의하고 구현은 포함하지 않는다.
ㅇ 런타임에 다형성을 제공한다.
ㅇ 특정 메서드 집합을 가진 객체들의 공통적인 동작을 추상화한다.

 

ㅁ 제네릭

ㅇ 제네릭은 다양한 데이터 타입에 대해 동작하는 코드를 작성할 수 있게 해 준다.

package main

import "fmt"

// 제네릭 함수: 두 값 중 더 큰 값을 반환
func Max[T comparable](a, b T) T {
	if a > b {
		return a
	}
	return b
}

func main() {
	fmt.Println(Max(10, 20))         // 정수 비교
	fmt.Println(Max(3.14, 2.71))    // 실수 비교
	fmt.Println(Max("apple", "cat")) // 문자열 비교 (사전 순서)
}

# 출력
20
3.14
cat

ㅇ Max 함수는 제네릭으로 정의되어 comparable 제약 조건을 만족하는 모든 타입(T)에 대해 동작한다.
ㅇ 정수, 실수, 문자열 등 다양한 타입에 대해 동일한 함수를 사용할 수 있다.

컴파일 시점에 타입 안정성을 제공한다.

ㅇ 다양한 타입에 대해 동작하는 함수나 데이터 구조를 만들 수 있다.

ㅇ 타입에 구애받지 않는 재사용 가능한 코드를 작성하여 코드 중복을 줄이고 타입 안전성을 유지하면서 유연성을 높인다.

 

ㅁ 인터페이스와 제네릭 비교 예제

ㅇ 같은 문제를 각각 인터페이스와 제네릭으로 해결하는 방식을 비교해보자.

인터페이스를 사용한 정렬 함수

package main

import (
	"fmt"
	"sort"
)

// Sortable 인터페이스 정의
type Sortable interface {
	sort.Interface // Go 표준 라이브러리의 sort.Interface 사용 가능
}

// 정렬 함수 (인터페이스 기반)
func PrintSorted(data Sortable) {
	sort.Sort(data)
	fmt.Println(data)
}

type IntSlice []int

func (s IntSlice) Len() int           { return len(s) }
func (s IntSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] }

func main() {
	data := IntSlice{5, 3, 8, 1}
	PrintSorted(data)
}

 

제네릭을 사용한 정렬 함수

package main

import (
	"fmt"
	"sort"
)

// 제네릭 기반 정렬 함수
func PrintSorted[T any](data []T, less func(i, j T) bool) {
	sort.Slice(data, func(i, j int) bool { return less(data[i], data[j]) })
	fmt.Println(data)
}

func main() {
	data := []int{5, 3, 8, 1}
	PrintSorted(data, func(i, j int) bool { return i < j }) // 정렬 기준 전달

	strData := []string{"banana", "apple", "cherry"}
	PrintSorted(strData, func(i, j string) bool { return i < j }) // 문자열 정렬 기준 전달
}

 

ㅁ 주요 차이점

특징 인터페이스 제네릭
목적 행동(메서드 집합)을 추상화하여 다형성을 제공
다양한 데이터 타입에 대해 재사용 가능한 코드를 작성
추상화 수준 행동
타입
사용 방식 특정 메서드 집합을 구현하도록 요구
컴파일 시점에 타입 매개변수를 사용하여 다양한 타입 지원
유연성 런타임에 동작하며 다양한 구현체를 처리 가능
컴파일 시점에 타입 안정성을 보장하며 고정된 데이터 구조나 알고리즘에 적합
예제 적용 범위 도형의 면적 계산(Shape)과 같은 공통 행동 정의
데이터 정렬(PrintSorted)이나 최대값 계산(Max)과 같은 일반적인 알고리즘 구현
적용 시점 주로 런타임에 다형성을 제공
컴파일 시점에 타입 안정성을 보장

 

 

ㅁ 마무리

인터페이스는 객체 지향적인 접근 방식으로 특정 행동을 추상화하며 다양한 구현체를 처리할 때 유용하다.
제네릭은 반복적인 코드를 줄이고 다양한 데이터 타입을 처리할 때 적합하며 컴파일 시점에서 타입 안정성을 제공한다.
두 개념은 상호 배타적이지 않으며 함께 사용할 수 있습니다. 예를 들어, 제네릭을 활용한 인터페이스 기반 데이터 구조를 설계하면 더욱 강력하고 유연한 코드를 작성할 수 있다. 예를 들어, 제네릭 인터페이스를 정의하여 타입 안정성과 다형성을 동시에 얻을 수 있다.

 

ㅁ 함께 보면 좋은 사이트

[Must Have] Tucker의 Go 언어 프로그래밍 - Go 제네릭의 이해

예제로 배우는 Go 프로그래밍 - 인터페이스

[OOP] interface - Go와 함께 알아보는 인터페이스(error, io package)

공봉식 - Tucker의 GO 언어 프로그래밍

구글 도서 - Tucker의 GO 언어 프로그래밍

반응형
Comments