- 20장 인터페이스
- 21장 함수고급편
20장 인터페이스
- 인터페이스의 구조는 아래소스와 같이 type 인터페이스명 interface { } 로 선언한다.
- type 키워드로 선언하기때문에 변수선언이 가능하다.
- 아래의 소스에서 Stringer라는 메서드를 만들었다. Student를 생성하여 , 해당 student를 stringer에 대입하여, stringer의 String()메서드를 호출하는 예제이다.
//ch20/ex20.1/ex20.1.go
package main
import "fmt"
type Stringer interface { // ❶ Stringer 인터페이스 선언
String() string
}
type Student struct {
Name string
Age int
}
func (s Student) String() string { // ❷ Student의 String() 메서드
return fmt.Sprintf("안녕! 나는 %d살 %s라고 해", s.Age, s.Name) // ❸ 문자열 만들기
}
func main() {
student := Student{"철수", 12} // Student 타입
var stringer Stringer // Stringer 타입
stringer = student // 4 stringer값으로 student 대입
fmt.Printf("%s\n", stringer.String()) // 5 stringer의 String() 메서드 호출
}
- 아래 소스는 Fedex에서 제공한 패키지를 사용하여, Fedex전송객체를 만들어 SendBook()을 호출하는 프로그램이다.
//ch20/ex20.2/ex20.2.go
package main
import "github.com/tuckersGo/musthaveGo/ch20/fedex"
func SendBook(name string, sender *fedex.FedexSender) {
sender.Send(name)
}
func main() {
// Fedex 전송 객체를 만듭니다.
sender := &fedex.FedexSender{}
SendBook("어린 왕자", sender)
SendBook("그리스인 조르바", sender)
}
//ch20/fedex/fedex.go
// Fedex에서 제공한 패키지입니다.
package fedex
import "fmt"
// Fedex에서 제공한 패키지 내 전송을 담당하는 구조체입니다.
type FedexSender struct {
}
func (f *FedexSender) Send(parcel string) {
fmt.Printf("Fedex sends %v parcel\n", parcel)
}
- Fedex에서 제공한 fedex패키지를 사용하지 많고, 이번에는 우체국에서 제공한 패키지를 사용해보자. 그러면 아래와 같이 타입이 맞지 않아 오류가 발생한다.
//ch20/ex20.3/ex20.3.go
package main
import (
"github.com/tuckersGo/musthaveGo/ch20/fedex"
"github.com/tuckersGo/musthaveGo/ch20/koreaPost"
)
func SendBook(name string, sender *fedex.FedexSender) {
sender.Send(name)
}
func main() {
// 우체국 전송 객체를 만듭니다.
sender := &koreaPost.PostSender{} // ❶ *koreaPost.PostSender 타입
SendBook("어린 왕자", sender) // ❷ 타입이 맞지 않습니다.
SendBook("그리스인 조르바", sender)
}
//ch20/koreaPost/post.go
// 우체국에서 제공한 패키지입니다.
package koreaPost
import "fmt"
// 우체국에서 제공한 패키지 내 전송을 담당하는 구조체입니다.
type PostSender struct {
}
func (k *PostSender) Send(parcel string) {
fmt.Printf("우체국에서 택배 %v를 보냅니다.\n", parcel)
}
- 아래와 같이 Sender인터페이스를 사용하여, Fedex, KoreaPost둘다 사용이 가능하다.
- 둘다 Send메서드가 있어서, 인터페이스를 통해서 각각의 Send()메서드가 호출된다.
//ch20/ex20.4/ex20.4.go
package main
import (
"github.com/tuckersGo/musthaveGo/ch20/fedex"
"github.com/tuckersGo/musthaveGo/ch20/koreaPost"
)
// ❶ Sender 인터페이스를 만들었습니다.
type Sender interface {
Send(parcel string)
}
// ❷ Sender 인터페이스를 입력으로 받습니다.
func SendBook(name string, sender Sender) {
sender.Send(name)
}
func main() {
// ❸ 우체국 전송 객체, Fedex 전송 객체 모두 SendBook 인수로 사용할 수 있습니다.
// 우체국 전송 객체를 만듭니다.
koreaPostSender := &koreaPost.PostSender{}
SendBook("어린 왕자", koreaPostSender)
SendBook("그리스인 조르바", koreaPostSender)
// Fedex 전송 객체를 만듭니다.
fedexSender := &fedex.FedexSender{}
SendBook("어린 왕자", fedexSender)
SendBook("그리스인 조르바", fedexSender)
}
- 아래 소스와 같이 빈 인터페이스(v interface{})를 인수로 받을수있다.
- v.(type) 을 사용하여 해당 타입에 맞는 로직을 수행할 수 있다.
//ch20/ex20.5/ex20.5.go
package main
import "fmt"
func PrintVal(v interface{}) { // ❶ 빈 인터페이스를 인수로 받는 함수
switch t := v.(type) {
case int:
fmt.Printf("v is int %d\n", int(t))
case float64:
fmt.Printf("v is float64 %f\n", float64(t))
case string:
fmt.Printf("v is string %s\n", string(t))
default:
// 그외 타입인 경우 타입과 값을 출력합니다.
fmt.Printf("Not supported type: %T:%v\n", t, t)
}
}
type Student struct {
Age int
}
func main() {
PrintVal(10) // int
PrintVal(3.14) // float64
PrintVal("Hello") // string
PrintVal(Student{15}) // Student
}
- 인터페이스 변수의 기본값은 nil값이기때문에 바로 사용하면 오류(런타임에러)가 발생한다.
//ch20/ex20.6/ex20.6.go
package main
type Attacker interface {
Attack()
}
func main() {
var att Attacker // ❶ 기본값은 nil입니다.
att.Attack() // ❷ att가 nil이기 때문에 런타임 에러가 발생합니다.
}
- 아래의 소스에서는 Stringer 인터페이스, Student 구조체, Student가 리시버인 String()메서드가 있다.
- main에서 PrintAge로 Student인 s를 호출하면, PrintAge에서는 Stringer리시버로 받는다.
- stringer 리시버를 Student로 타입변환하여 , Student의 Age를 출력하는 예제이다.
//ch20/ex20.7/ex20.7.go
package main
import "fmt"
type Stringer interface { // ❶ 인터페이스
String() string
}
type Student struct { // ❷ 구조체
Age int
}
func (s *Student) String() string { // ❸ Student 타입의 String() 메서드
return fmt.Sprintf("Student Age:%d", s.Age)
}
func PrintAge(stringer Stringer) { // ➍
s := stringer.(*Student) // ➎ *Student 타입으로 타입 변환
fmt.Printf("Age: %d\n", s.Age) // ➏ s.Age 출력
}
func main() {
s := &Student{15} // ➐ *Student 타입 변수 s 선언 및 초기화
PrintAge(s) // ➑ 변수 s 를 인터페이스 인수로 PrintAge() 함수 호출
}
- 아래의 경우에는 Student구조체가 String() 메서드를 포함하고 있지 않기때문에 컴파일 오류가 발생한다.
//ch20/ex20.8/ex20.8.go
package main
type Stringer interface {
String() string
}
type Student struct {
}
func main() {
var stringer Stringer
stringer.(*Student) // ❶ 빌드 타임 에러 발생
}
- 아래의 소스에서는 Actor, Student 둘다 String()메서드가 있지만, actor를 인자로 받아 Student로 변경할수없다.
//ch20/ex20.9/ex20.9.go
package main
import "fmt"
type Stringer interface {
String() string
}
type Student struct {
}
func (s *Student) String() string {
return "Student"
}
type Actor struct {
}
func (a *Actor) String() string {
return "Actor"
}
func ConvertType(stringer Stringer) {
// ❷ 런타임 에러 발생: *Student 타입은 Stringer 인터페이스로 쓰일 수 있지만
// stringer값이 *Student 타입이 아니기 때문에 에러가 발생합니다.
student := stringer.(*Student)
fmt.Println(student)
}
func main() {
// ❶ *Actor 구조체 값을 ConvertType() 함수의 인수로 사용합니다.
actor := &Actor{}
ConvertType(actor)
}
- File은 Read()를 메서드로 갖는 구조체이다. ReadFile에서 Closer로 타입변환하면 오류가 발생한다.
//ch20/ex20.10/ex20.10.go
package main
type Reader interface {
Read()
}
type Closer interface {
Close()
}
type File struct {
}
func (f *File) Read() {
}
func ReadFile(reader Reader) {
// ❷ Reader 인터페이스 변수를 Closer 인터페이스로 타입 변환합니다.
// 런타임 에러가 발생합니다.
c := reader.(Closer)
c.Close()
}
func main() {
// ❶ File 포인터 인스턴스를 ReadFile() 함수의 인수로 사용합니다.
file := &File{}
ReadFile(file)
}
21장 함수고급편
- 아래 소스는 가변인수함수이다. ... 키워드를 사용하여 가변인수를 처리할수있다.
- 해당 내용은 7장에서 설명하였다.
- https://developer-com.tistory.com/2#function
//ch21/ex21.1/ex21.1.go
package main
import "fmt"
func sum(nums ...int) int { // ❶ 가변 인수를 받는 함수
sum := 0
fmt.Printf("nums 타입: %T\n", nums) // // ❷ nums 타입 출력
for _, v := range nums {
sum += v
}
return sum
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5)) // 5개의 인수를 사용합니다.
fmt.Println(sum(10, 20)) // 2개의 인수를 사용합니다.
fmt.Println(sum()) // 0개의 인수를 사용합니다.
}
- defer 에 대한 예제이다. 이 명령역시 7장에서 설명하였다.
//ch21/ex21.2/ex21.2.go
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("test.txt") // ❶ 파일 생성
if err != nil { // ❷ 에러 확인
fmt.Println("Failed to create a file")
return
}
defer fmt.Println("반드시 호출됩니다.") // ❸ 지연 수행될 코드
defer f.Close() // ➍ 지연 수행될 코드
defer fmt.Println("파일을 닫았습니다") // ➎ 지연 수행될 코드
fmt.Println("파일에 Hello World를 씁니다.")
fmt.Fprintln(f, "Hello World") // ➏ 파일에 텍스트를 씁니다.
}
함수타입변수
- getOperator의 return 타입은 func(int, int) int 의 함수타입이다.
- 해당 함수의 리턴값은 함수를 리턴한다.
- 예제에서는 getOperator("*")를 호출하는데 * 이므로 mul() 함수를 반환하여, operator() 는 mul()과 같다.
//ch21/ex21.3/ex21.3.go
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func mul(a, b int) int {
return a * b
}
func getOperator(op string) func(int, int) int { // ❶ op에 따른 함수 타입 반환
if op == "+" {
return add
} else if op == "*" {
return mul
} else { // ❷ +나 *가 아니면 nil 반환
return nil
}
}
func main() {
// ❸ int 타입 인수 2개를 받아서 int 타입 반환을 하는 함수 타입 변수
var operator func(int, int) int
operator = getOperator("*")
var result = operator(3, 4) // ❹ 함수 타입 변수를 사용해서 함수 호출
fmt.Println(result)
}
함수리터럴
- 아래소스는 위의소스와 같은 기능을 하는데, return값에 익명함수를 바로 적는다. 함수이름이 없기때문에 익명함수 또는 람다라고 하는데, Go에서는 함수리터럴이라고 부른다.
//ch21/ex21.4/ex21.4.go
package main
import "fmt"
type opFunc func(a, b int) int
func getOperator(op string) opFunc {
if op == "+" {
return func(a, b int) int { // ❶ 함수 리터럴을 사용해서 더하기 함수를 정의하고 반환
return a + b
}
} else if op == "*" {
return func(a, b int) int { // ❷ 함수 리터럴을 사용해서 곱하기 함수를 정의하고 반환
return a * b
}
} else {
return nil
}
}
func main() {
fn := getOperator("*")
result := fn(3, 4) // ❸ 함수 타입 변수를 사용해서 함수 호출
fmt.Println(result)
}
- 아래의 경우에 결과값은 11이 된다. 함수리터럴은 외부변수의 값을 바꿀수있다.
//ch21/ex21.5//ex21.5.go
package main
import "fmt"
func main() {
i := 0
f := func() {
i += 10 // ❶ i에 10 더하기
}
i++
f() // ❷ f 함수 타입 변수를 사용해서 함수 리터럴 실행
fmt.Println(i)
}
- 함수리터럴은 외부변수를 내부상태로 가져온다.(Capture) 캡쳐는 값복사가 아닌 참조형태로 가져오기때문에 주의가 필요하다.
- 아래는 캡쳐를 하기때문에 발생할수있는 문제에 대한 에제이다.
//ch21/ex21.6/ex21.6.go
package main
import "fmt"
func CaptureLoop() {
f := make([]func(), 3)
fmt.Println("CaptureLoop")
for i := 0; i < 3; i++ {
f[i] = func() {
fmt.Println(i)
}
}
for i := 0; i < 3; i++ {
f[i]()
}
}
func CaptureLoop2() {
f := make([]func(), 3)
fmt.Println("CaptureLoop2")
for i := 0; i < 3; i++ {
v := i
f[i] = func() {
fmt.Println(v)
}
}
for i := 0; i < 3; i++ {
f[i]()
}
}
func main() {
CaptureLoop()
CaptureLoop2()
}
- 아래 예제에서는 writeHello함수가 있는데 인자로 넘어온값이 func(string)의 함수형인자이다.
- writeHello 에서는 해당 write 함수를 호출한다.
//ch21/ex21.7/ex21.7.go
package main
import (
"fmt"
"os"
)
type Writer func(string)
func writeHello(writer Writer) { // ❷ writer 함수타입 변수 호출
writer("Hello World")
}
func main() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Println("Failed to create a file")
return
}
defer f.Close()
writeHello(func(msg string) {
fmt.Fprintln(f, msg) // ❶ 함수 리터럴 외부 변수 f 사용
})
}
'Go' 카테고리의 다른 글
[묘공단] Tucker의 Go 언어 프로그래밍 24장~25장 (0) | 2024.02.03 |
---|---|
[묘공단] Tucker의 Go 언어 프로그래밍 22장~23장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 18장~19장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 15장~17장 (0) | 2024.01.29 |
[묘공단] Tucker의 Go 언어 프로그래밍 12장~14장 (0) | 2024.01.29 |