18. 인터페이스
18. 인터페이스
💡 인터페이스를 어떤 구체 타입으로 변경하는 것은 Type Assertion이라고 한다! (Type Conversion, 타입 변환과는 다르다)
✅ 0. 추가 내용
- 아래 내용에서 나오는
타입 변환
은Type Assertion
이다. Type Conversion(타입 변환)과는 다른 개념이므로 혼용하면 안된다 !!!
1.1 Type assertion (형 단언)
- 대상은 반드시 인터페이스 값이어야 함. (콘크리트 → 콘크리트는 assertion 불가)
- 런타임에 v의 동적 타입이 T인지 검사.
- 실패 시 panic, 안전하게 하려면 comma, ok 사용.
1.2 Type conversion (형 변환)
- 인터페이스 아니어도 됨. 대부분의 “호환 가능한” 타입 사이에서 명시적으로 변환.
- 의미: C의 캐스트처럼 비트 재해석이 아니라, 목표 타입의 규칙에 맞춘 새 값 생성(때로는 복사/할당 발생).
- 실패는 보통 컴파일 에러(불가능한 변환)이며, 런타임 panic은 아님.
✅ 1. 인터페이스
- 인퍼테이스를 이용하면 메서드 구현을 포함한 구체화된 객체가 아닌 추상화된 객체로 상호작용할 수 있다
1 2 3 4
type DuckInterface interface { Fly() Walk(distance int) int }
type
키워드, 인터페이스명,intefface
키워드를 통해 선언한다- 내부 블록에는 메서드 집합을 작성한다.
- 메서드는 반드시 메서드명이 있어야 한다
- 매개변수와 반환이 다르더라도 동일한 메서드명을 가질 수 없다
- 인터페이스에서는 메서드 구현을 포함하지 않는다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"
type Stringer interface {
String() string
}
type Student struct {
Name string
Age int
}
func (s Student) String() string {
return fmt.Sprintf("%v is %d years old", s.Name, s.Age)
}
func main() {
student := Student{"Minsu", 12}
var stringer Stringer = student
fmt.Printf("%s\n", stringer.String()) // Minsu is 12 years old
}
- Stringer 인터페이스는 String() string 메서드를 포함한다. → string 타입을 반환하는 String() 메서드를 구현하는 모든 타입은 Stringer 인터페이스로 사용될 수 있다
- 인터페이스 내의 모든 메서드를 구현해야 인터페이스로 사용될 수 있다
✅ 2. 인터페이스 왜 쓰나?
- 인터페이스는 객체지향 프로그램에서 아주 중요한 역할을 한다
- 인터페이스를 이용하면 구체화된 객체가 아닌 인터페이스로 메서드를 호출할 수 있기 때문에 큰 코드 수정 없이 구체화된 객체만 바꿔서 사용할 수 있게 된다
✅ 3. 덕 타이핑
만약 어떤 새를 봤는데 그 새가 오리처럼 걷고 오리처럼 날고 오리처럼 소리내면 나는 그 새를 오리라고 부르겠다
- implements와 같은 키워드를 사용하지 않고, 인터페이스에 정의된 메서드들을 모두 포함한다면 그 인터페이스로 간주한다는 방식
- 덕 타이핑에서는 인터페이스 구현 여부를 타입 선언 시 하는게 아니라 인터페이스가 사용될 때 결정한다. 따라서 서비스 제공자가 인터페이스를 정의할 필요 없이 서비스 이용자가 필요에 따라 인터페이스를 정의해서 사용할 수 있다
✅ 4. 인터페이스 기능 더 알기
5.1 포함된 인터페이스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Reader interface {
Read() (n int, err error)
Close() error
}
type Writer interface {
Write() (n int, err error)
Close() error
}
type ReadWriter interface {
Reader
Writer
}
- 다른 인터페이스를 포함하는 인터페이스를 선언하면 인터페이스들의 메서드 집합을 모두 포함한다
- 위 예시처럼 동일한 메서드가 있다면 하나의 메서드만 인터페이스에 포함된다
5.2 빈 인터페이스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"
func PrintVal(v interface{}) { // any로 대체 가능
switch t := v.(type) {
case int:
fmt.Printf("v is int %d\n", int(t))
case float64:
fmt.Printf("v is float64 %d\n", float64(t))
case string:
fmt.Printf("v is string %d\n", string(t))
default:
fmt.Printf("Not Supported Type: %T:%v\n", t, t)
}
}
func main() {
PrintVal(10)
PrintVal("Hello")
PrintVal(6.4)
}
- 빈 인터페이스는
interface{}
와 같이 사용하며 어떤 값이든 받을 수 있는 함수, 메서드, 변수값을 만들 때 사용한다v.(type)
는 switch문 내부에서만 사용 가능한 문법이다
Go 1.18부터interface{}
는 미리 선언된 별칭인any
로 대체 가능하다
5.3 인터페이스 기본값
- 인터페이스 변수의 가본값은 nil이다
- 구현체가 없어 메모리 주솟값이 nil이기 때문에 런타임 에러가 발생한다
1
2
3
4
5
6
7
8
9
10
package main
type Attacker interface {
Attack()
}
func main() {
var att Attacker
att.Attack() // panic: runtime error: invalid memory address
}
✅ 6. 인터페이스 변환하기
6.1 구체화된 타입으로 변환
v.(Type)
형식으로 인터페이스 변수를 구체화된 타입으로 변환할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import "fmt"
type Stringer interface {
String() string
}
type Student struct {
Age int
}
func (s *Student) String() string {
return fmt.Sprintf("Age: %v\n", s.Age)
}
func PrintAge(stringer Stringer) {
s := stringer.(*Student)
fmt.Printf("Age: %d\n", s.Age)
}
func main() {
s := &Student{15}
PrintAge(s)
}
Stringer
인터페이스는 메서드밖에 없기 때문에 인터페이스 변수에서Age
필드에 직접 접근할 수 없다s := stringer.(*Student)
로 변환 후 필드에 접근할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import "fmt"
type Stringer interface {
String() string
}
type Student struct {
}
func (s *Student) String() string {
return "Student"
}
type Actor struct {
}
func (a *Actor) String() string {
return "Actor"
}
func Convert(stringer Stringer) {
student := stringer.(*Student)
fmt.Println(student)
}
func main() {
var a *Actor = &Actor{}
Convert(a) // panic: interface conversion: main.Stringer is *main.Actor, not *main.Student
}
*Student
타입과*Actor
타입 모두 String() 메서드를 구현해 Stringer 인터페이스로 사용할 수 있다- 하지만 Conver 메서드의 인자로 받은 stringer에
*Actor
타입 인스턴스가 들어있기 때문에 런타임 에러가 발생한다
6.2 다른 인터페이스로 타입 변환
- 인터페이스가 가리키고 있는 실제 인스턴스가 다른 인터페이스를 포함하는 경우 인터페이스 간 타입 변환이 가능하다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main
type Reader interface {
Read()
}
type Writer interface {
Write()
}
type File struct {
}
func (f *File) Read() {
}
func ReadFile(reader Reader) {
w := reader.(Writer) // panic: interface conversion: *main.File is not main.Writer: missing method Write
w.Write()
}
func main() {
file := &File{}
ReadFile(file)
}
- File 구조체가 Reader 인터페이스는 구현했지만 Writer 인터페이스는 구현하지 않았다
ReadFile(reader Reader)
메서드에서 Reader 인터페이스 → Writer 인터페이스간 변환은 컴파일 시점에는 문제가 없지만(실제 인스턴스가 두 인터페이스를 모두 구현했는지 확인 불가) reader 인터페이스 변수가*File
타입 인스턴스를 가리키므로 Writer 인터페이스를 사용할 수 없어 런타임 에러가 발생한다
1
2
3
4
5
6
func ReadFile(reader Reader) {
w, ok := reader.(Writer)
if ok {
w.Write()
}
}
- 타입 변환 시 변환 성공 여부를 같이 받을 수 있으며, 이 때는 타입 변환이 불가능하더라도 두 번째 반환값이
false
로 반환될 뿐 런 타임 에러는 발생하지 않는다
1
2
3
if w, ok := reader.(Writer); ok {
...
}
- 많은 개발자가 이렇게 한 줄로 줄여서 표현하는 것을 더 선호한다
This post is licensed under CC BY 4.0 by the author.