golang 기초 - 인터페이스

한나리·2025년 1월 9일

Go

목록 보기
11/19
post-thumbnail

인터페이스

인터페이스를 이용하면 메서드 구현을 포함한 구체화된 객체가 아닌 추상화된 객체로 상호작용 할 수 있음.

인터페이스 선언

type (인터페이스명) interface{
	메서드 집합
}

인터페이스도 구조체처럼 타입 중 하나이기 때문에 type을 써줘야 함
이 말은 인터페이스 변수 선언이 가능하고 변수의 값으로 사용할 수 있다는 뜻

🔎 인터페이스 안에 메서드 집합을 써줄 때 세가지 유의 사항
1️⃣ 메서드는 반드시 메서드명이 있어야 한다.
2️⃣ 매개변수와 반환이 다르더라도 이름이 같은 메서드는 있을 수 없다.
3️⃣ 인터페이스에서는 메서드 구현을 포함하지 않는다.

package main

import "fmt"

type Stringer interface {
	String() string
}
type Student struct {
	Name string
	Age  int
}

func (s Student) String() string {
	return fmt.Sprintf("안녕! 나는 %d살 %s라고 해", s.Age, s.Name)
}
func main() {
	student := Student{"철수", 12}
	var stringer Stringer

	stringer = student

	fmt.Printf("%s\n", stringer.String())
}

stringer 값으로 Student타입 변수 student를 대입 할 수 있는 이유는,
stringer는 Stringer 인터페이스 이고, Student 타입은 String() 메서드를 포함하고 있기 때문에 stringer값으로 student를 대입할 수 있음.

Sprintf() 함수는 서식에 따라 문자열을 만들어서 반환하는 함수
Printf() 함수가 서식에 따라 문자열을 터미널에 출력하는 함수라면, Sprintf()는 화면에 출력하는 것이 아닌 string 타입으로 반환

💡 인터페이스를 쓰는 이유

구체화된 객체가 아닌 인터페이스만 가지고 메서드를 호출할 수 있기 때문에 큰 코드 수정 없이 필요에 따라 구체화된 객체를 바꿔서 사용할 수 있게됨

예제로 이해하기

  • fedex의 Send()
package fedex

import "fmt"

type FedexSender struct {
}

func (f *FedexSender) Send(parcel string) {
	fmt.Printf("Fedex sends %v parcel \n", parcel)
}
  • fedex의 Send()
package fedex

import "fmt"

type FedexSender struct {
}

func (f *FedexSender) Send(parcel string) {
	fmt.Printf("Fedex sends %v parcel \n", parcel)
}
  • KoreaPost의 send()
package koreaPost

import "fmt"

type PostSender struct {
}

func (f *PostSender) Send(parcel string) {
	fmt.Printf("우체국에서 택배 %v를 보냅니다. \n", parcel)
}

책을 보내는 함수를 만들어보자

package main

type FedexSender struct {
}

func SendBook(name string, sender *fedex.FedexSender) {
	sender.Sender(name)
}

func main() {
	sender := &fedex.FedexSender{}
	SendBook("어린 왕자", sender)
	SendBook("그리스인 조르바", sender)
}

이렇게 만들게 되면 fedex에 종속된 SendBook()함수가 되고, KoreaPost를 사용할 수 없다.

package main

type PostSender struct {
}

func SendBook(name string, sender *koreaPost.PostSender) {
	sender.Sender(name)
}

func main() {
	sender := &koreaPost.FedexSendPostSenderer{}
	SendBook("어린 왕자", sender)
	SendBook("그리스인 조르바", sender)
}

_이렇게 만들게 되면 KoreaPost에 종속된 SendBool()함수가 되고, fedex를 사용할 수 없다.

그렇다고, sendBookByKoreaPost() / sendBoolByFedex() 이렇게 만드는 것은 너무 비효율적이다.
이럴때, 문제가 되는 sender의 타입을 인터페이스로 만들면 깔끔하게 해결 할 수 있다._

package main

type Sender interface {
	Send(parcel string)
}

func SendBook(name string, sender Sender) {
	sender.Sender(name)
}

func main() {
	fedexSender := &koreaPost.FedexSendPostSenderer{}
	SendBook("어린 왕자", sender)
	SendBook("그리스인 조르바", sender)

	koreaPostSender := &koreaPost.PostSender{}
	SendBook("어린 왕자", sender)
	SendBook("그리스인 조르바", sender)
}

Sender 인터페이스는, Send 메서드만 구현하고 있다면 모든 타입이 허용되는 것이기 때문에 코드를 매우 유연하게 사용할 수 있다.

추상화 계층

내부 동작을 감춰서 서비스 제공하는 쪽과 사용하는 쪽 모두에게 자유를 주는 방식을 추상화라고 함.

인터페이스는 추상화를 제공하는 추상화계층
이렇게 추상화 계층을 이용해 의존 관계를 끊는 것을 디커플링이라고 함
의존성은 낮을수록 좋음!

profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글