인터페이스를 이용하면 메서드 구현을 포함한 구체화된 객체가 아닌 추상화된 객체로 상호작용 할 수 있음.
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 타입으로 반환
구체화된 객체가 아닌 인터페이스만 가지고 메서드를 호출할 수 있기 때문에 큰 코드 수정 없이 필요에 따라 구체화된 객체를 바꿔서 사용할 수 있게됨
package fedex
import "fmt"
type FedexSender struct {
}
func (f *FedexSender) Send(parcel string) {
fmt.Printf("Fedex sends %v parcel \n", parcel)
}
package fedex
import "fmt"
type FedexSender struct {
}
func (f *FedexSender) Send(parcel string) {
fmt.Printf("Fedex sends %v parcel \n", parcel)
}
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 메서드만 구현하고 있다면 모든 타입이 허용되는 것이기 때문에 코드를 매우 유연하게 사용할 수 있다.
내부 동작을 감춰서 서비스 제공하는 쪽과 사용하는 쪽 모두에게 자유를 주는 방식을 추상화라고 함.
인터페이스는 추상화를 제공하는 추상화계층
이렇게 추상화 계층을 이용해 의존 관계를 끊는 것을 디커플링이라고 함
의존성은 낮을수록 좋음!