728x90

Go언어는 이 때까지의 코딩 패러다임을 거부하고 자신들의 철학을 주장하는 언어이다.

이 언어는 어떤면에서는 굉장히 C스러운 면이 있다.


이번에 소개할 녀석은 다른 언어를 하셨다면 상속과 유사하다.

아니, 사실 유사한건 아니고 상속과 매우 비슷한 느낌으로 사용할 뿐, 매커니즘은 다르다. 아주 다르다.

바로 인터페이스라는 녀석이다.


다른언어를 하셨으면 인터페이스가 낯설지 않다. 자바의 인터페이스 포스팅을 보면 인터페이스에 대한 설명이 나온다.

그러나 Go에서는 이러한 다른 인터페이스와 이 인터페이스는 다르다.

이 차이는 먹는 배와 신체부위 배의 차이만큼이나 다르다.

무슨말이냐고? 그냥 다른거라는 의미다.


예시를 보자.


package main

import "fmt"

type Square struct {
Width float32
Height float32
}

type Triangle struct {
Width float32
Height float32
}

func (s Square) Area() float32 {
return s.Width * s.Height
}

func (t Triangle) Area() float32 {
return t.Width * t.Height / 2
}

func main() {
s := Square{2, 5}
t := Triangle{2, 5}
fmt.Println(s.Area())
fmt.Println(t.Area())
}

여러분은 사각형(Square)와 삼각형(Triangle)이라는 객체를 만들었다.

이 객체는 둘다 도형(Figure)라고 할 수도 있다. 왜냐하면 도형의 특징인 넓이를 가지기 때문이다.

일단 두 객체를 만들었지만 둘의 넓이를 구하는 식이 다르다.

그러므로 우리는 각기 다른 메소드를 만들어야한다.

뭐 이는 당연한 일이다.


그러나 이런식으로 하면 뭔가 조금 아쉽다.

만약 원이 추가된다면? 타원이 추가된다면? 오각형이 추가된다면?

어짜피 도형이라는 큰 틀로 묶이는데 더 상위 개념으로 묶을 수는 없는 걸까?

그런 개념에서 시작해서 나온것이 바로 다른 언어의 상속 개념이다.


다른 언어 시간이 아니니 상속을 논하진 않겠다. 필자의 자바포스팅 상속에 대한 설명을 읽어보라.

이 상속모델은 여러가지 장점을 가지기에 객체지향을 논할때 빠지지 않는 요소이다.

참고로 객체지향에 대한 설명은 필자의 이 포스팅을 참조하길 바란다.


우리는 저기 넓이를 가지는 녀석들을 도형이라는 걸로 묶어주고 싶어


그렇다면 이제 인터페이스의 세계로 초대할 때가 되었다.


type Figure interface {
Area() float32
}

인터페이스 역시 선언 하는 방식은 struct와 유사하다.

다만 차이점이 있다면 안에는 메소드와 반환형을 기재한다.

현재 예시는 메소드가 하나인데 하나가 아니라 struct처럼 여러개도 상관없다.

그리고 이렇게 만들면 이제 끝난다.

엄청 간단하지 않은가?

이제 여러분은 아래와 같은 코드를 짤 수 있다.


func main() {
var s, t Figure
s = Square{2, 5}
t = Triangle{2, 5}
fmt.Println(s.Area())
fmt.Println(t.Area())
}

s,t를 Figure형으로 선언했다.

그러면 생성자를 호출할 때도 반드시 자신의 타입을 맞춰야한다.

그런데 Figure로 선언했지만 Squre나 Triangle이 붙는게 가능하다.

아직까지 안와닿는가?

그럼 이렇게 하면 와닿는가?


func main() {
f := make([]Figure, 0)
f = append(f, Square{2, 5})
f = append(f, Triangle{2, 5})
for _, value := range f {
fmt.Println(value.Area())
}

}

f를 Figrure슬라이스로 선언해서 거기에 Squre와 Triangle을 넣는게 가능하다.

이게 가능한 이유는 Squre와 Triangle모두 Area라는 메소드가 존재하기 때문이다.

보통 Golang진형에서는 이를 interface는 암시적으로 충족된다.라고 한다.

쉽게 말해서 우리가 명시적으로 Squre는 Figure에 관련되있다라고 해주지 않아도 알아서 찾는다는 뜻이다.


type Line struct{
Length float32
}

가령 위 같은 코드를 보자.

Line형이 선언됬다.


Line을 Figure슬라이스에 넣는게 될까?


정답은 안된다이다.

그 이유는 Line은 Area라는 메소드가 없기 때문이다.


struct가 interface형으로 구현될려면 interface에 정의된 메소드가 struct에 존재해야한다.


그리고 또한 아래와 같은 재밌는 상황이 가능하다.



상황을 좀더 세분화해서 보자.


type GetName interface {
hisName()
}

type GetAge interface {
hisAge()
}

type InfoPerson interface {
GetName
GetAge
}

다른거 필요없이 이 부분이 가장 중요하다.

InfoPerson 인터페이스는 GetName과 GetAge를 모두 가져온다.

그러면 이게 무슨효과가 나오냐면 여러분이 아마 예상은 하겠지만 GetName과 GetAge의 메소드를 모두 포함하게된다.

즉 interface가 내부에 interface를 선언하면 그 선언된 interface의 메소드를 가져오게된다.


func main()  {
var i InfoPerson
i = Person{"Kukaro",27}
i.hisName()
i.hisAge()
}

그렇기 때문에 위의 코드에서 i는 InfoPerson형이고

또한 hisName과 hisAge형을 직접 선언한 적은 없지만 interface내부에 interface를 선언함으로써 이게 가능하게 한다.

여기서 interface를 멤버로 가지는(is-a) 방식은 흔히 우리가 말하는 상속과 아주 유사하다.

사실 상속이라고 생각해도 무방할 정도다. 그래서  Golang을 하는 사람들이 상속을 이야기한다면 이런 방식으로 사용하는 것이다.

엄밀히 말하면 상속은 아니지만 그것도 받아들이는 사람 나름인거 같다.



+ Recent posts