Go/Golang - Interface
Interface 기본
Interface 선언 형태
1
2
3
4
5
6
7
8
9
| // type interface_name interface_keyword
type employeeInterface interface {
Work()
Rest(hour int) int
// method 선언 시 주의사항
// 1 메서드는 반드시 메서드명이 있어야 한다.
// 2 Overload 안된다.
// 3 인터페이스에서는 메서드 구현을 포함하지 않는다.
}
|
Interface 사용이유
- Interface 를 사용하면
구체적으로 구현한 객체가 아닌, Interface
만으로 메소드를 호출할 수 있다. 따라서 필요에 따라 객체를 갈아 끼우기 용이하다. 또한 그 과정에서 발생하는 코드의 수정량도 매우 적어지며, 이 Interface를 사용하는 로직
입장에서는 안에 어떤 객체가 들어오는 지 알아야 할 필요 없이 코드를 유연하게 사용할 수 있다. - Interface 는 이런 측면에서
Abstraction Layer(추상화 계층)
이다. 또한 이러한 계층을 통해 서비스 제공자
와 서비스 사용자
간의 연결고리를 끊는 것을 decoupling(디 커플링)
이라고 한다.
Interface 추가 기능
Inner Interface
Interface 안에 Interface를 인자로 가질 수 있다. 하지만 구현체가 Interface 안의 모든 메소드를 구현해야지 만이 그 Interface로 사용될 수 있다는 점을 명심하자!
빈 Interface {} 는 어디다 쓰나?
빈 Interface인 interface{}
는 어떤 값이는 받을 수 있는 함수, 메소드 등을 만들 때 사용한다. 예를 들어 함수의 인자로 빈 interface를 받으면, 해당 interface의 타입에 따라 다른 로직이 동작하게 만들수 도 있다.
Interface의 기본값
nil 값이다. 따라서 Interface를 사용할 때에는 nil값인지 여부를 꼭 확인하고 사용해야한다.
Interface 변환
Interface를 다른 타입으로 변환하기
Interface 본래의 구체화된 타입으로 복원하는 경우 많이 사용한다. 만약 같은 interface를 공유하는 다른 개체를 원 객체로 되돌렸는데, 해당 인터페이스가 *Student를 가르키지 않거나, 구현하고있지 않아도 에러가 발생한다. 즉, 내부적으로 어떤 인스턴스를 가르키고 있었는지 또한 중요한 문제이다.
1
2
| var testItf Interface
origin := testItf.(beforeType)
|
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
| type Stringer interface{
String() string
}
type Student struct{
Age int
}
func (s *Student) String() string {
return fmt.Sprintf("Student Age: %d", s.Age)
}
func PrintAge(stringer Stringer){
s := stringer.(*Student)
fmt.Printf("age : %d\n", s.Age)
}
func main(){
// Student 구조체 초기화.
s := &Student{15}
// PrintAge함수 호출
// Stringer 인터페이스 변수인 s를 -> Struct로 변환해 -> Age로 접근하고자 함.
// (Stringer 인터페이스는 Age를 가지고 있지 않기 때문에 본래 타입인 Struct로 변환하지 않으면 접근불가)
// stringer 변수 내부에서 *Student를 가리키고 있으므로 무사히 변환 가능
// 만약 같은 interface를 공유하는 다른 개체를 원 객체로 되돌렸는데, 해당 인터페이스가 *Student를 가르키지 않거나,
// 구현하고있지 않아도 에러가 발생한다. -> 즉 내부적으로 어떤 인스턴스를 가르키고 있었는지 또한 중요한 문제이다.
PrintAge(s)
}
|
Interface를 다른 인터페이스로 타입변환하기
인터페이스가 인터페이스로 변환하는 경우에는 변경 전 인터페이스를 포함하지 않아도 된다. 하지만 변경하려고 하는 인터페이스를 근본 인터페이스가 포함하고 있는 상태여야 한다.
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
33
34
35
36
37
38
39
40
| package main
type Reader interface{
Read()
}
type Closer interface{
Close()
}
type File struct{
}
func(f *File) Read(){
}
// func(f *File) Close(){
// }
func ReadFile(reader Reader){
c := reader.(Closer)
c.Close()
// sol 1.
// c, ok := reader.(Closer)
// if ok{
// c.Close()
// }
// sol 2.
// if c, ok := reader.(Closer); ok{
// c.Close()
// }
}
func main(){
file := &File{}
ReadFile(file)
}
|
Questions
Q1. Interface를 사용하는 이유는 무엇인가.
1
2
3
4
| Interface 를 사용하면 `구체적으로 구현한 객체가 아닌, Interface` 만으로 메소드를 호출할 수 있다.
따라서 필요에 따라 객체를 갈아 끼우기 용이하다. 또한 그 과정에서 발생하는 코드의 수정량도 매우 적어지며,
이 `Interface를 사용하는 로직` 입장에서는 안에 어떤 객체가 들어오는 지 알아야 할 필요 없이 코드를
유연하게 사용할 수 있다 (Abstraction Layer 이자 Decoupling 작용)
|
Q2. Duck-typing이란 무엇인지, 또 장점은 무엇인지 설명하라.
1
2
3
4
5
| Interface 구현 여부를 타입 선언 시점에 명시적으로 나타낼 필요없이, 인터페이스에 정의한 메서드의 포함여부 만으로
특정 인터페이스를 포함하는지 여부를 결정하는 것. `Duck-typing` 을 지원하지 않았다면 java와 같이 특정 클래스가
어떤 인터페이스를 구현하고 있는지를 표시해야 했다. 이 부분은 `인터페이스 지원 여부를 사용자가 아닌, 제공 대상자가
직접 작성해야 했으므로 사용자 중심의 코딩이 불가능했다.` 이 부분을 사용자 중심 즉, 인터페이스 적용여부를 사용자가
선택할 수 있도록 구성된 환경이 바로 `Duck-Typing` 이다.
|
Q3. 아래 코드를 에러가 발생하지 않도록 고쳐라.
추가로, 런타임 에러를 잡을수 있는 형태의 코드도 추가하라.
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
33
34
35
36
37
38
39
40
| package main
type Reader interface{
Read()
}
type Closer interface{
Close()
}
type File struct{
}
func(f *File) Read(){
}
// func(f *File) Close(){
// }
func ReadFile(reader Reader){
c := reader.(Closer)
c.Close()
// sol 1.
// c, ok := reader.(Closer)
// if ok{
// c.Close()
// }
// sol 2.
// if c, ok := reader.(Closer); ok{
// c.Close()
// }
}
func main(){
file := &File{}
ReadFile(file)
}
|