본문 바로가기
Go

[Go] 인터페이스(Interface)

by weero 2022. 3. 5.

 

[Tucker의 Go 언어 프로그래밍] 책을  참고

 

인터페이스

인터페이스란 구현을 포함하지 않는 메서드 집합이다. 인터페이스만 가지고 메서드를 호출할 수 있어 추후 프로그램 요구사항 변경 시 유연하게 대처할 수 있다. 인터페이스를 이용하면 메서드 구현을 포함한 구체화된 객체가 아닌 추상화된 객체로 상호작용할 수 있다.

type DuckInterface interface {
	Fly()
    Walk(distance int) int
}

 

인터페이스 타입의 변수 선언이 가능하고 변수의 값으로 사용할 수 있다.

 

 

인터페이스에 포함된 메서드 집합의 유의사항

  1. 메서드는 반드시 메서드명이 있어야 한다.
  2. 매개변수와 반환이 다르고 이름은 같은 메서드는 있을 수 없다.
  3. 인터페이스에서는 메서드 구현을 포함하지 않는다.

 

package main

import "fmt"

type Stringer interface {
	String() string
}

type Student struct {
	Name string
	Age int
}

func (s Student) String() string { // Student의 String() 메서드
	return fmt.Sprintf("안녕! 나는 %d살 %s라고 해", s.Age, s.Name) // 문자열 만들기
}

func main() {
	student := Student{"철수", 12} // 구조체 Student 타입
	var stringer Stringer        // 인터페이스 Stringer 타입

	stringer = student // 인터페이스 stringer 값으로 student 대입

	fmt.Printf("%s\n", stringer.String()) // stringer의 String() 메서드 호출
}

 

 

인터페이스 사용 이유

인터페이스만 가지고 메서드를 호출할 수 있기 때문에 큰 코드 수정 없이 필요에 따라 구체화된 객체를 바꿔서 사용할 수 있게 된다. 프로그램의 변경 요청에 유연하게 대처할 수 있는 것이다.

 

package main

import (
	"github.com/tuckersGo/musthaveGo/ch20/koreaPost"
	"github.com/tuckersGo/musthaveGo/ch20/fedex"
)

type Sender interface {
	Send(parcel string)
}

// Sender 인터페이스를 입력으로 받는다
func SendBook(name string, sender Sender) {
	sender.Send(name)
}

func main() {
	// 우체국 전송 객체, Fedex 전송 객체 모두 SendBook 인수로 사용할 수 있다.
	// 우체국 전송 객체를 만든다.
	koreaPostSender := &koreaPost.PostSender{}
	SendBook("어린 왕자", koreaPostSender)
	SendBook("그리스인 조르바", koreaPostSender)

	// Fedex 전송 객체를 만든다.
	fedexSender := &fedex.FedexSender{}
	SendBook("어린 왕자", fedexSender)
	SendBook("그리스인 조르바", fedexSender)
}

 

인터페이스 사용을 통해 내부 구현은 알 수 없고, 오직 인터페이스 즉 메서드 집합만 알 수 있다. 내부 동작을 감춰서 서비스를 제공하는 쪽과 사용하는  쪽 모두에게 자유를 주므로 인터페이스는 추상화를 제공하는 추상화 계층이다.

 

 

인터페이스를 포함하는 인터페이스

인터페이스도 다른 인터페이스를 포함할 수 있다. 이를 포함된 인터페이스라 부른다.

// Read()와 Close() 메서드를 포함한 Reader 인터페이스
type Reader interface {
	Read() (n int, err error)
	Close() error
}

// Write() 메서드와 Close() 메서드를 포함한 Writer 인터페이스
type Writer interface {
	Write() (n int, err error)
	Close() error
}

// Reader, Writer 인터페이스의 메서드 집합을 모두 포함한 ReadWriter 인터페이스
// Read(), Write(), Close() 메서드를 가지게 된다.
type ReadWriter interface {
	Reader // Reader의 메서드 집합을 포함합니다.
	Writer // Writer의 메서드 집합을 포함합니다.
}

 

 

 

빈 인터페이스 interface{}를 인수로 받기

가지고 있어야 할 메서드가 하나도 없기 때문에 모든 타입이 빈 인터페이스로 쓰일 수 있다. 빈 인터페이스는 모든 타입을 받을 수 있는 함수, 메서드, 변숫값을 만들 때 사용한다.

 

 

인터페이스 기본값 nil

인터페이스 변수의 기본값은 유효하지 않은 메모리 주소를 나타내는 nil이다. 인터페이스는 초깃값이 없기 때문에 런타임 에러를 주의해야 한다. (invalid memory address)

 

 

 

인터페이스 변환하기

인터페이스 변수를 타입 변환을 통해 구체화된 다른 타입이나 다른 인터페이스로 변환할 수 있다.

 

구체화된 다른 타입으로 타입 변환하기

var a Interface
t := a.(ConcreteType)

보통 인터페이스 본래의 구체화된 타입으로 복원할  때 주로 사용한다.

 

package main

import "fmt"

type Stringer interface {
	String() string
}

type Student struct {
	Age int
}

func (s *Student) String() string {
	return fmt.Sprintf("Student Age: %d\n", s.Age)
}

func PrintAge(stringer Stringer) {	// *Student를 Stringer 인터페이스로 받는다.
	// 구조체 포인터 *Student 타입은 String() 메서드를 가지고 있다. 따라서 Stringer 인터페이스 인수로 쓸 수 있는 것이다.
	fmt.Printf("%s", stringer.String())
	s := stringer.(*Student)		// 인터페이스를 본래의 구체화된 타입으로 복원할 때 주로 쓴다.
	fmt.Printf("Age: %d\n", s.Age)
}

func main() {
	s := &Student{15}
	PrintAge(s)
}

 

stringer 변수 내부에서 *Student 인스턴스를 가리키고 있어 *Student 타입으로 타입 변환이 가능하다.

인터페이스 변수를 구체화된 타입으로 타입 변환하려면 해당 타입이 인터페이스 메서드 집합을 포함하고 있어야 한다.

 

 

다른 인터페이스로 타입 변환하기

다른 인터페이스로 타입 변환을 할 수 있다. 변경되는 인터페이스가 변경 전 인터페이스를 포함하지 않아도 되지만, 인터페이스가 가리키고 있는 실제 인스턴스가 변환하고자 하는 다른 인터페이스를 포함해야 한다.

 

위 말을 쉽게 풀이하자면

ConcreteType이 AInterface와 BInterface를 포함하고 있을 때 ConcreteType 인스턴스를 가리키고 있는 AInterface 변수 a는 BInterface로 타입 변환이 가능하다는 뜻이다.

(변경되는 인터페이스(BInterface)가 변경 전 인터페이스(AInterface)를 포함하지 않아도 되지만 인터페이스(AInterface)가 가리키고 있는 실제 인스턴스(a)가 변환하고자 하는 다른 인터페이스(BInterface)를 포함해야 한다)

 

var a AInteface = ConcreteType{}
b := a.(BInteface)

 

 

타입 변환 성공 여부 반환

var a Interface
t, ok := a.(ConcreteType)

t : 타입 변환 결과

ok : 변환 성공 여부