Yeom's
Yeom's Coding Note
Yeom's
전체 방문자
오늘
어제
  • 분류 전체보기 (262)
    • 백준온라인 (94)
    • 운영체제 (14)
    • 프로그래머스 (0)
    • Go Language (40)
    • AWS (1)
    • 자료구조 (21)
    • Effective Java 3E (41)
    • JPA (1)
    • Tech Interview (33)
    • MySQL 8.0 (7)
    • CS 기술면접 (7)
    • Linux (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • boj 2839
  • RealMySQL 8.0
  • https://mangkyu.tistory.com/88
  • https://mangkyu.tistory.com/90
  • maeil-mail
  • https://mangkyu.tistory.com/91
  • https://mangkyu.tistory.com/89
  • https://mangkyu.tistory.com/94
  • 교보dts
  • 백준 설탕 배달
  • 망나니개발자
  • m1 linux
  • 출처 : 망나니 개발자
  • https://mangkyu.tistory.com/93
  • Real MySQL 8.0
  • java
  • UTM
  • 망나니 개발자
  • https://mangkyu.tistory.com/92

최근 글

티스토리

hELLO · Designed By 정상우.
Yeom's

Yeom's Coding Note

Go Language

[Go] #17 인터페이스

2023. 1. 14. 18:10
728x90
반응형

메소드의 집합 인터페이스

'구조체와 메소드' 챕터에서 구조체와 메소드가 프로그래밍을 할 때 없어서는 안되는 이유와 그들의 기능에 대해 배웠다.

이번 챕터에서 다룰 내용은 구조체, 메소드와 일맥상통하는 주제인 '인터페이스'이다. 구조체는 같은 속성의 필드들의 집합체이고, 메소드는 함수 중에서도 구조체의 속성을 기능적으로 수행하는 특별한 함수라고 했다. '메소드' 강의에서 삼각형의 넓이를 구하는 예시 코드로 공부를 했다. 인터페이스의 필요성을 흐름으로 알아보기 위해 이 예시를 다시 한 번 천천히 짚어보자. 

 

삼각형의 넓이를 구하기 위해 필요한 변수는 '너비'와 '높이'이다. 그래서 다수의 삼각형 정보를 저장하기 위해 너비와 높이의 정보를 각각 따로 선언하는 것이 아니라 '삼각형의 정보'를 담는 구조체를 선언해 높이와 너비를 필드로 선언했다. 그리고 '삼각형의 넓이'를 연산하는 기능의 함수를 만들어야 하는데 이는 삼각형의 정보 구조체를 이용한 기능만 수행하는 것이기 때문에 특정 구조체 전용 기능인 메소드로 따로 선언했다. 그래서 삼각형의 정보들인 구조체 객체를 이용해 넓이를 연산하는 메소드를 사용함으로써 완벽한 프로그램이라고 생각했다. 

 

그런데 프로그램을 확장하다 보면 삼각형의 넓이 뿐만이 아니라 둘레, 무게중심 기능이 추가될 수 있다. 더 나아가 원, 사각형과 같은 다양한 구조체들과 그에 따른 기능들이 추가되면 어떻게 될 것 같을까? 어던 객체에 어던 메소드를 사용해야 하는지 굉장히 헷갈리게 될 것이다. 따라서 변수를 묶은 구조체만 필요한 것이 아니라, 메소드를 모아놓은 '인터페이스'도 필요하다.

 

물론 인터페이스는 메소드들의 집합체로서 같은 속성의 기능을 하는 메소드들을 한눈에 보기 편하다는 점이 있다. 하지만 '단순히 명시만 하는 기능인가?'에 대한 부분에서 의문점이 생길 수 있다.

예를 들어 구조체로

  • '원'의 정보
  • '사각형'의 정보

가 있고 이 구조체를 이용해 넓이를 구하는 메소드가 있다고 생각해보자. 기능은 같지만 두 구조체의 필드가 다르고 연산 방법도 다르기 때문에 메소드도 두 개를 선언해야 한다. 따라서 이름은 같지만 내용물이 다른 메소드를 만들어야 한다.

 

우선 아래에 인터페이스를 사용하지 않고 두 객체의 넓이를 출력하는 예시 코드를 확인해보자.

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
package main
 
import (
    "fmt"
    "math" //Pi를 사용하기 위해 import함
)
 
type Rect struct {
    width, height float64
}
 
type Circle struct {
    radius float64
}
 
func (r Rect) area() float64 {
    return r.width * r.height
}
 
func (c Circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
 
func main() {
    r1 := Rect{10, 20}
    c1 := Circle{10}
 
    fmt.Println(r1.area())
    fmt.Println(c1.area())
}
Colored by Color Scripter
cs

위 코드는 사각형의 너비와 높이 정보를 필드로 가지는 Rect 와 원의 반지름 정보를 필드로 가지는 Circle 구조체가 있다.

그리고 두 구조체의 정보를 이용해 넓이를 구하는 기능을 하는 area 메소드가 두 개 있다. 두 개의 area 메소드는 공통적으로 넓이를 구하는 기능을 하지만 전달받는 구조체 객체와 그에 따른 연산 과정이 다르기 때문에 각각 따로 선언했다. 전달받는 구조체가 다르기 때문에 메소드의 이름이 동일하게 선언되어도 괜찮다. 그리고 r1 과 c1 에 각각 사각형과 원의 구조체를 선언 및 초기화하고 area 메소드를 실행하고 출력한다. 

 

만약 많은 수의 객체의 넓이를 출력하려면 어떻게 해야 할까? 전달하는 객체의 형(구조체)이 다르기 때문에 같은 기능의 메소드지만 매개변수를 다르게 설정해줘야한다. 그래서 같은 속성의 기능을 묶어놓은 인터페이스를 활용하면 인터페이스에 있는 메소드들을 호출할 수 있다.

인터페이스를 활용해 위 예시 코드를 수정해보았다.

 

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
41
42
package main
 
import (
    "fmt"
    "math"
)
 
type geometry interface { //인터페이스 선언 Reat와 Circle 메도스의 area를 모두 포함
    area() float64
}
 
type Rect struct {
    width, height float64
}
 
type Circle struct {
    radius float64
}
 
func (r Rect) area() float64 {
    return r.width * r.height
}
 
func (c Circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
 
 
func main() {
    r1 := Rect{10, 20}
    c1 := Circle{10}
    r2 := Rect{12, 14}
    c2 := Circle{5}
 
    printMeasure(r1, c1, r2, c2)
}
 
func printMeasure(m ...geometry) { //인터페이스를 가변 인자로 하는 함수
    for _, val := range m { //가변 인자 함수의 값은 슬라이스형
        fmt.Println(val.area()) //인터페이스의 메소드 호출
    }
}
Colored by Color Scripter
cs

위 코드에서 인터페이스는 매개변수로 선언이 되어 따로 선언하지 않았다. 매개변수로 인터페이스를 사용한다는 것은 구조체와 관계없이 인터페이스에 포함된 메소드를 사용하겠다는 뜻이다.

 

좀 더 복잡해진 아래 코드를 확인해보자.

 

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
41
42
43
44
45
46
47
48
49
50
51
52
package main
 
import (
    "fmt"
    "math"
)
 
type geometry interface {
    area() float64
    perimeter() float64 // 둘레를 측정하는 메소드 추가
}
 
type Rect struct {
    width, height float64
}
 
type Circle struct {
    radius float64
}
 
func (r Rect) area() float64 {
    return r.width * r.height
}
 
func (c Circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
 
func (r Rect) perimeter() float64 { // 둘레를 측정하는 메소드 추가
    return 2 * (r.width + r.height)
}
 
func (c Circle) perimeter() float64 { // 둘레를 측정하는 메소드 추가
    return 2 * math.Pi * c.radius
}
 
func main() {
    r1 := Rect{10, 20}
    c1 := Circle{10}
    r2 := Rect{12, 14}
    c2 := Circle{5}
 
    printMeasure(r1, c1, r2, c2)
}
 
func printMeasure(m ...geometry) {
    for _, val := range m {
        fmt.Println(val)
        fmt.Println(val.area())
        fmt.Println(val.perimeter())
    }
}
Colored by Color Scripter
cs

빈 인터페이스(Empty Interface)

함수부터 인터페이스까지 숨 가쁘게 다양한 용법에 대해 배웠다. 그래서 지금쯤 한 가지 간과하는 부분이 있을 수 있다. 바로 형(Type)에 관한 것이다. 함수와 구조체는 형으로 쓸 수 있다. 이것에 관한 내용은 '익명 함수'강의에서 '일급 함수'에 대해 배울 때 알았다. 결국 구조체도 형을 사용자 정의로 사용하는 것이고 함수도 특별한 것이 아니라 형으로 사용할 수 있다고 했다. 인터페이스도 마찬가지이다. 빈 인터페이스에 대해 알아보기 위해 몇 가지 특징을 알아보자.

  • 인터페이스는 내용을 따로 선언하지 않아도 형으로서 사용할 수 있다.
  • 인터페이스는 하나의 형이기 때문에 매개변수로 사용될 수 있다.
  • 인터페이스는 어떠한 타입도 담을 수 있는 컨테이너이다. 즉 'Dynamic type'이다.

예를 들어 어떤 변수에 순서대로 string형을 저장해 출력하고 int형을 저장해 출력한다고 생각해보자. 형이 다르기 때문에 다른 매개변수형을 가지는 함수를 만들고, 형이 달라짐에 따라 변수도 새롭게 초기화해야 한다. 하지만 빈 인터페이스 형을 쓰면 어떠한 형도 담을 수 있어 편하게 사용할 수 있다.

아래에 빈 인터페이스를 활용한 간단한 예시 코드를 확인해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
 
import "fmt"
 
func printVal(i interface{}) {
    fmt.Println(i)
}
 
func main() {
    var x interface{} //빈 인터페이스 선언
 
    x = 1
    printVal(x)
  
    x = "test"
    printVal(x)
}
Colored by Color Scripter
cs

위 예시에서는 간단한 코드의 형태로 사용해봤지만 앞으로 Go언어를 접하면 빈 인터페이스를 많이 볼 수 있을 것이다. 이는 다양한 형을 담기 위한 Dynamic type이라는 것을 잘 알아두자.

 

Type Assertion

Assertion은 '주장'이라는 뜻이다. 인터페이스형으로 선언된 변수는 초기화하는 값에 따라 형이 자동 명시되지만 사실 위에서 언급했다시피 Dynamic type이다. 다라서 확실한 형을 표현하기 위해서 'Type Assertion'을 할 필요가 있다. 인터페이스 형에서 형을 선언하기 위해서는 "변수이름.(형)"을 명시하면 된다. 주의해야 할 점은 인터페이스 형으로 선언됐는데 nil 값인 경우 에러가 발생한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
 
import "fmt"
 
func main() {
    var num interface{} = 10
 
    a := num       
    b := num.(int)
 
    fmt.Printf("%T,%d\n",a,a)
    printtest(b)
}
 
func printtest (i interface{}){
    fmt.Printf("%T,%d\n",i,i)
}
cs

 

728x90
반응형

'Go Language' 카테고리의 다른 글

[Go] #18-2 defer와 panic()  (0) 2023.01.17
[Go] #18-1 지연 처리 defer  (0) 2023.01.17
[Go] #16 메소드  (0) 2023.01.14
[Go] #15 구조체  (0) 2023.01.14
[Go] #14 클로저  (0) 2023.01.11
    'Go Language' 카테고리의 다른 글
    • [Go] #18-2 defer와 panic()
    • [Go] #18-1 지연 처리 defer
    • [Go] #16 메소드
    • [Go] #15 구조체
    Yeom's
    Yeom's

    티스토리툴바