Go 언어는 강력한 타입 시스템을 가지고 있으며, 인터페이스는 이러한 타입 시스템의 핵심 개념 중 하나입니다.
인터페이스는 코드의 재사용성을 높이고 다형성을 지원하여 유연하고 확장 가능한 코드를 작성하는데 도움을 줍니다.
이러한 인터페이스를 알아보며 덕타이핑과 어댑터를 통하여 인터페이스의 활용방법 등에 대하여 설명해 보겠습니다.
목차
인터페이스란?
어댑터 패턴이란?
인터페이스란?
인터페이스는 메서드의 집합을 정의하는 타입입니다. struct와 달리 필드를 가지지 않고 메서드 시그니처만 포함합니다.
struct처럼 interface도 type으로 선언합니다.
interface 생성
다른 언어에서도 사용하였던 인터페이스지만 Go언어는 항상 새롭게 느껴집니다.
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
위의 코드에서 'Shape'라는 인터페이스를 생성하였고 'Circle'이라는 struct와 'Circle'의 메서드인 'Area'를 정의하였습니다.
interface에 메서드는 이름과 매개변수 반환 타입만 작성합니다.
interface 사용
Go 언어는 interface를 implements 하여 사용하지 않고 interface가 가지고 있는 메서드를 포함하는 type은 interface로 사용 가능합니다. 즉 'Circle'의 메서드를 'Shape'로 접근하여 사용할 수 있습니다.
func main(){
testCircle := Circle{Radius: 5}
var testShape Shape = testCircle
fmt.Println(testShape.Area())
// 출력78.53981633974483
}
'Shape'가 가진 메서드를 'Circle'이 전부 가지고 있으므로 'Shape 타입의 변수의 값'으로 'Circle 타입의 변수'를 선언할 수 있습니다.
testShape는 testCircle이므로 interface의 메서드인 Area를 실행하면 Circle의 Area를 실행하여 출력됩니다.
또한 함수는 call by value이므로 아래와 같이 interface를 사용할 수 있습니다.
func PrintArea(s Shape) {
fmt.Println("Area:", s.Area())
}
func main() {
circle := Circle{Radius: 5}
PrintArea(circle) // 출력: 78.53981633974483"
}
s의 값이 매개변수로 전달한 'Circle 타입의 변수'로 선언되어 Area의 결과를 출력하게 됩니다.
어댑터 패턴이란?
어댑터 패턴은 기존의 인터페이스를 다른 인터페이스로 변환해 주는 디자인 패턴입니다. 예를 들어, 서드파티 라이브러리나 외부 시스템의 인터페이스를 내부에서 사용하는 인터페이스로 변환할 때 유용합니다.
Adapter를 만드는 방법은 다양하고 필요에 따라 달라지므로 아래의 예시코드를 보시고 이렇게도 사용할 수 있구나 생각해 주시면 감사하겠습니다.
예시 코드
package main
import (
"fmt"
)
// Printer1
type LegacyPrinter struct{}
func (lp *LegacyPrinter) Print(s string) {
fmt.Println("Legacy Printer:", s)
}
// Printer1 Adapter
type LegacyPrinterAdapter struct{
la LegacyPrinter
}
func (l LegacyPrinterAdapter) PrintStored(s ...string){
for _,v :=range s {
l.la.Print(v)
break
}
}
// Printer2
type NewPrinter struct{}
func (np *NewPrinter) newPrint(s1, s2 string) {
fmt.Printf("newPrinter로 출력 : %s 그리고 %s \n",s1, s2)
}
// Printer2 Adapter
type NewAdapter struct{
na NewPrinter
}
func (n NewAdapter) PrintStored(s ...string){
for i,_ :=range s {
n.na.newPrint(s[i],s[i+1])
break
}
}
// interface
type ModernPrinter interface {
PrintStored(s ...string)
}
// interface 사용
func Print (m ModernPrinter, s ...string){
m.PrintStored(s...)
}
func main(){
// Printer 선언
legacy := LegacyPrinter{}
legacy.Print("legacy입니다.")
new := NewPrinter{}
new.newPrint("첫 번째 string","두 번째 string")
// Adapter 선언
legacyAdapter := LegacyPrinterAdapter{legacy}
legacyAdapter.PrintStored("legacy Adapter입니다.","Adapter로 프린팅 중입니다.")
NewAdapter := NewAdapter{new}
NewAdapter.PrintStored("newAdapter 입력1","newAdapter 입력2")
// interface 사용
Print(legacyAdapter,"출력1","출력2","출력3")
Print(NewAdapter,"출력1","출력2","출력3")
}
LegacyPrinter와 NewPrinter 두 가지 Printer는 출력에 대하여 메서드 명과 매개변수의 수가 다른 메서드를 가지고 있습니다.
따라서 interface를 만들 때 위의 두 프린터가 필요로 하는 매개변수의 개수가 달라 가변인자를 사용하면 매개변수를 관리하기 편합니다.
필요한 변수의 종류와 개수에 따라 잘 선택하여 생성해 주시면 됩니다.
이렇게 만든 interface로 관리하기 위하여 PrintStored를 사용하기 위한 Adapter를 만들어줍니다.
Adapter가 PrintStored를 가지고 있기에 Adapter를 통하여 PrintStored를 사용합니다.
출력
Legacy Printer: legacy입니다.
newPrinter로 출력 : 첫 번째 string 그리고 두 번째 string
Legacy Printer: legacy Adapter입니다.
newPrinter로 출력 : newAdapter 입력1 그리고 newAdapter 입력2
Legacy Printer: 출력1
newPrinter로 출력 : 출력1 그리고 출력2
Print에 매개변수를 각각 3개씩 넣었지만 Adapter에서 변수의 개수를 조절하여 출력하도록 하여 문제없이 출력되는 것을 볼 수 있습니다.
'Go language' 카테고리의 다른 글
[Go] 채널 이해하기 (콘텍스트) (0) | 2023.08.28 |
---|---|
[Go] 고루틴 이해하기 (뮤텍스, 데드락) (0) | 2023.08.27 |
[Go] 메서드 사용법 (함수와 메서드의 차이) (0) | 2023.08.25 |
[Go] Pointer이해하기 (call by value 주의사항) (0) | 2023.08.25 |
[Go] 구조체와 임베디드 구조체 (0) | 2023.08.24 |