Menu
Coddy logo textTech

인터페이스를 통한 다형성

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 41번째.

다형성(Polymorphism)은 공유된 인터페이스를 통해 서로 다른 타입들이 동일하게 처리될 수 있도록 합니다. Go에서 이는 상속이나 클래스 계층 구조 없이 전적으로 인터페이스를 통해 구현됩니다.

함수가 인터페이스 타입을 매개변수로 받을 때, 해당 인터페이스를 구현하는 모든 구체 타입(concrete type)을 전달할 수 있습니다. 함수는 구체적인 타입을 알 필요가 없으며, 오직 인터페이스에 정의된 동작에만 관심을 가집니다:

type Speaker interface {
    Speak() string
}

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

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

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

이제 MakeSoundSpeak() 메서드를 가진 모든 타입에서 작동합니다:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // 멍멍!
    MakeSound(cat)  // 야옹!
}

동일한 함수 호출이 전달된 실제 타입에 따라 서로 다른 동작을 생성합니다. 이것이 바로 다형성의 실제 사례입니다. MakeSound 함수는 한 번 작성되지만, Speaker 인터페이스를 충족하는 한 무제한의 타입들과 함께 작동합니다.

이 접근 방식은 코드를 유연하고 확장 가능하게 유지해 줍니다. 말을 하는 새로운 타입을 추가할 때 기존 함수를 변경할 필요가 없습니다. 단지 인터페이스를 구현하기만 하면 자동으로 작동합니다.

challenge icon

챌린지

쉬움

다형성이 실제로 어떻게 작동하는지 보여주는 차량 설명 시스템을 구축해 보겠습니다. 인터페이스를 통해 공통된 동작을 공유하는 서로 다른 차량 유형을 만들고, 모든 차량에서 작동하는 단일 함수를 작성하게 됩니다.

코드는 다음 두 개의 파일로 구성됩니다:

  • vehicles.go: Describe() string 메서드를 요구하는 Describer 인터페이스를 정의합니다. 그런 다음 이 인터페이스를 각자의 방식으로 구현하는 세 가지 차량 유형을 만듭니다:
    • BrandModel 필드를 가진 Car—이 유형의 Describe()Car: [Brand] [Model]을 반환합니다.
    • BrandEngineCC (int) 필드를 가진 Motorcycle—이 유형의 Describe()Motorcycle: [Brand] [EngineCC]cc를 반환합니다.
    • Type 필드(예: "Mountain" 또는 "Road")를 가진 Bicycle—이 유형의 Describe()Bicycle: [Type]을 반환합니다.
  • main.go: 임의의 Describer를 인자로 받아 Describe()를 호출한 결과를 출력하는 PrintDescription 함수를 만듭니다. 입력에서 차량 상세 정보를 읽어 각 차량 유형의 인스턴스를 하나씩 생성하고, 각각을 PrintDescription에 전달하여 동일한 함수가 세 가지 서로 다른 유형 모두에서 작동함을 증명합니다.

다음과 같은 입력이 제공됩니다:

  • 1행: 자동차 브랜드 (Car brand)
  • 2행: 자동차 모델 (Car model)
  • 3행: 오토바이 브랜드 (Motorcycle brand)
  • 4행: 오토바이 엔진 배기량 (Motorcycle engine CC, 정수)
  • 5행: 자전거 유형 (Bicycle type)

예를 들어, Toyota, Camry, Honda, 600, Mountain이 주어지면 출력은 다음과 같아야 합니다:

Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountain

PrintDescription이 전달받는 대상이 Car인지, Motorcycle인지, Bicycle인지 알 필요가 없다는 점에 주목하세요. 단순히 Describe()를 호출하면 각 유형이 자신만의 고유한 출력으로 응답합니다. 이것이 바로 다형성입니다: 하나의 함수, 여러 가지 동작.

치트 시트

다형성을 사용하면 공유된 인터페이스를 통해 서로 다른 타입들을 통일된 방식으로 다룰 수 있습니다. Go에서는 상속이나 클래스 계층 구조 없이 인터페이스를 통해 이를 구현합니다.

함수가 인터페이스 타입을 매개변수로 받을 때, 해당 인터페이스를 구현하는 모든 구체 타입(concrete type)을 전달할 수 있습니다.

type Speaker interface {
    Speak() string
}

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

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

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

동일한 함수가 서로 다른 타입들과 함께 작동합니다.

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

함수는 한 번만 작성되지만, 인터페이스를 만족하는 한 무제한의 타입들과 함께 작동합니다. 새로운 타입을 추가할 때 기존 함수를 수정할 필요가 없으며, 단지 인터페이스를 구현하기만 하면 됩니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// TODO: 임의의 Describer를 인자로 받는 PrintDescription 함수를 생성하세요
// 그리고 Describe()를 호출한 결과를 출력하세요

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 자동차 상세 정보 읽기
	carBrand, _ := reader.ReadString('\n')
	carBrand = strings.TrimSpace(carBrand)
	carModel, _ := reader.ReadString('\n')
	carModel = strings.TrimSpace(carModel)

	// 오토바이 상세 정보 읽기
	motoBrand, _ := reader.ReadString('\n')
	motoBrand = strings.TrimSpace(motoBrand)
	motoEngineStr, _ := reader.ReadString('\n')
	motoEngineStr = strings.TrimSpace(motoEngineStr)
	motoEngine, _ := strconv.Atoi(motoEngineStr)

	// 자전거 상세 정보 읽기
	bicycleType, _ := reader.ReadString('\n')
	bicycleType = strings.TrimSpace(bicycleType)

	// TODO: 입력값을 사용하여 Car, Motorcycle, Bicycle을 생성하세요

	// TODO: 다형성을 보여주기 위해 각 차량에 대해 PrintDescription을 호출하세요
	fmt.Println("TODO: Print vehicle descriptions")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨