GO language practical interface implementation and method set

write in front


  • Well, study GO, so I have this article
  • The content of the blog post is 《GO语言实战》one of the reading notes
  • mainly involves knowledge
    • What is the interface
    • Method set (value receiving and pointer receiving)
    • Polymorphism

In the evening, you sit under the eaves and watch the sky slowly getting dark. You feel lonely and desolate, feeling that your life has been deprived of you. I was a young man at the time, but I was afraid of living like this and growing old. In my opinion, this is more terrible than death. --------Wang Xiaobo


GolangInside 多态refers to the ability of code to take different behaviors depending on the specific implementation of the type.

如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值. There are good examples in the standard library, such as iothe streaming interface implemented in the package. ioThe package provides a set of well-constructed interfaces and functions to allow code to easily support streaming data processing.

只要实现两个接口, you can take advantage of ioall the powerful capabilities behind the entire package. However, our program will involve many details when declaring and implementing the interface. Even if you implement existing interfaces, you still need to understand how these interfaces work.

Before exploring how interfaces work and the details of their implementation, let's first look at an example of using an interface from the standard library.

standard library

curl functions such as

//  这个示例程序展示如何使用 io.Reader 和 io.Writer 接口
//  写一个简单版本的 curl 程序
package main

import (
 "fmt"
 "io"
 "net/http"
 "os"
)

// init 在 main 函数之前调用
func init() {
    
    
 if len(os.Args) != 2 {
    
    
  fmt.Println("Usage: ./example2 <url>")
  os.Exit(-1)
 }
}

// main 是应用程序的入口
func main() {
    
    
 // 从 Web 服务器得到响应
 r, err := http.Get(os.Args[1])
 if err != nil {
    
    
  fmt.Println(err)
  return
 }

 // 从 Body 复制到 Stdout
 io.Copy(os.Stdout, r.Body)
 if err := r.Body.Close(); err != nil {
    
    
  fmt.Println(err)
 }
}

http.ResponseThe type contains a Bodyfield named which is a io.ReadCloservalue of interface type

io.CopyThe second parameter of the function accepts a io.Readervalue of interface type, which represents the source of data inflow. BodyThe field implements io.Readerthe interface

io.CopyThe first parameter is the target to copy to. This parameter must be an implemented io.Writerinterface. osA special value in the package Stdoutindicates the standard output device, which has implemented io.Writerthe interface.

If you have learned languages ​​​​such as Java, it is easy to understand here. By analogy with IO reading and writing in Java, low-level streams are packaged into high-level streams for IO processing.

┌──[[email protected]]-[/usr/local/go/src]
└─$ go run listing34.go
Usage: ./example2 <url>
exit status 255
┌──[[email protected]]-[/usr/local/go/src]
└─$ go run listing34.go  http://localhost:80
<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8' content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
  .....

Demo below

// Sample program to show how a bytes.Buffer can also be used
// 用于 io.Copy 函数
package main

import (
 "bytes"
 "fmt"
 "io"
 "os"
)

// main is the entry point for the application.
func main() {
    
    
 var b bytes.Buffer

 // 将字符串写入 Buffer
 b.Write([]byte("Hello"))

 //  使用 Fprintf 将字符串拼接到 Buffer
 fmt.Fprintf(&b, "World!")

 // 将 Buffer 的内容写到 Stdout
 io.Copy(os.Stdout, &b)
}

fmt.FprintfThe function accepts an io.Writerinterface value of type as its first parameter, bytes.Buffera pointer of type implements io.Writerinterface, and bytes.Buffera pointer of type also implements io.Readerinterface, again using io.Copythe function

func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
    
    
 p := newPrinter()
 p.doPrintf(format, a)
 n, err = w.Write(p.buf)
 p.free()
 return
}

run code

┌──[[email protected]]-[/usr/local/go/src]
└─$ go run lsiting35.go
HelloWorld!

io.Writerand io.Readerinterface

type Writer interface {
    
    
 Write(p []byte) (n int, err error)
}
type Reader interface {
    
    
 Read(p []byte) (n int, err error)
}

bytes.BufferThe implementation of the corresponding interface above

func (b *Buffer) Write(p []byte) (n int, err error) {
    
    
 b.lastRead = opInvalid
 m, ok := b.tryGrowByReslice(len(p))
 if !ok {
    
    
  m = b.grow(len(p))
 }
 return copy(b.buf[m:], p), nil
}
....
func (b *Buffer) Read(p []byte) (n int, err error) {
    
    
 b.lastRead = opInvalid
 if b.empty() {
    
    
  // Buffer is empty, reset to recover space.
  b.Reset()
  if len(p) == 0 {
    
    
   return 0, nil
  }
  return 0, io.EOF
 }
 n = copy(p, b.buf[b.off:])
 b.off += n
 if n > 0 {
    
    
  b.lastRead = opRead
 }
 return n, nil
}

accomplish

接口is used to define 行为的类型. 行为These are not defined by 接口直接实现, but implemented by the user-defined type through methods. That is what we often call the implementation class

GOThe reason for this 实体类型is that without the internal storage of the user-defined type's implementation, the interface has no concrete behavior. Unlike Java, there are operations like default methods.

Not all values ​​are exactly equal, 用户定义的类型and the value or pointer must satisfy 接口的实现the requirements 遵守一些规则.

Shows userthe internal layout of the value of an interface variable after type value assignment. 接口值是一个两个字长度的数据结构,

  • The first word contains an 指向内部表的指针。这个内部表叫作iTable,包含了所存储的值的类型信息。iTable containing the type information of the stored value and a set of methods associated with this value.

  • The second word is a pointer to the stored value. Combining type information and pointers creates a special relationship between these two values.

insert image description here

Changes that occur after a pointer is assigned to an interface. In this case, the type information remains 存储一个指向保存的类型的指针, and the second word of the interface value still holds the pointer to the entity value.

insert image description here

method set

The method set defines the acceptance rules of the interface.

// 这个示例程序展示 Go 语言里如何使用接口
package main

import (
 "fmt"
)

// notifier 是一个定义了
// 通知类行为的接口
type notifier interface {
    
    
 notify()
}

// user 在程序里定义一个用户类型
type user struct {
    
    
 name  string
 email string
}

//  notify 是使用指针接收者实现的方法
func (u *user) notify() {
    
    
 fmt.Printf("Sending user email to %s<%s>\n",
  u.name,
  u.email)
}

// main is the entry point for the application.
func main() {
    
    
 // Create a value of type User and send a notification.
 u := user{
    
    "Bill", "[email protected]"}

 sendNotification(u)

func sendNotification(n notifier) {
    
    
 n.notify()
}

Although the program looks fine, it actually fails to compile

insert image description here

=============================================

GOROOT=C:\Program Files\Go #gosetup
GOPATH=C:\Users\liruilong\go #gosetup
"C:\Program Files\Go\bin\go.exe" build -o C:\Users\liruilong\AppData\Local\JetBrains\GoLand2023.2\tmp\GoLand\___go_build_listing36_go.exe C:\Users\liruilong\Documents\GitHub\golang_code\chapter5\listing36\listing36.go #gosetup
# command-line-arguments
.\listing36.go:32:19: cannot use u (variable of type user) as notifier value in argument to sendNotification: user does not implement notifier (method notify has pointer receiver)

Compilation finished with exit code 1

According to the prompt information we can see:

u (type is user) cannot be used as the parameter type of sendNotification. The type notifier:userdoes not implement notifier (the notify method is declared using a pointer receiver)

userTo understand why a value of type cannot implement an interface when using a pointer receiver , you need to first understand 方法集.

方法集定义了一组关联到给定类型的值或者指针的方法. The type used when defining a method 接收者determines whether the method is associated with a value, a pointer, or both.

The set of methods described in the specification

  • T --> (t T)
  • *T --> (t T) and (t *T)

In turn, look at the method set from the perspective of the receiver type:

This is only possible if you 指针接收者implement one using .接口指向那个类型的指针实现对应的接口

  • (t T) --> T and *T
type notifier interface {
    
    
 notify()
}

func (u *user) notify() {
    
    
 fmt.Printf("Sending user email to %s<%s>\n",
  u.name,
  u.email)
}

Need to use pointers

func main() {
    
    
 u := user{
    
    "Bill", "[email protected]"}
 sendNotification(&u)
}
func sendNotification(n notifier) {
    
    
 n.notify()

If used 值接收者to implement an interface, then all types 值和指针of实现对应的接口

  • (t *T) --> *T
type notifier interface {
    
    
 notify()
}

func (u user) notify() {
    
    
 fmt.Printf("Sending user email to %s<%s>\n",
  u.name,
  u.email)
}

That is, the following two methods are available

call by value

func main() {
    
    
 u := user{
    
    "Bill", "[email protected]"}
 sendNotification(u)
}
func sendNotification(n notifier) {
    
    
 n.notify()
}

pointer call

func main() {
    
    
 u := user{
    
    "Bill", "[email protected]"}
 sendNotification(&u)
}
func sendNotification(n notifier) {
    
    
 n.notify()
}

The question now is, why is there such a restriction?

package main

import "fmt"

// duration 是一个基于 int 类型的类型
type duration int

// 使用更可读的方式格式化 duration 值
func (d *duration) pretty() string {
    
    
	return fmt.Sprintf("Duration: %d", *d)
}

// main 是应用程序的入口
func main() {
    
    
	//d := duration(42)
	//d.pretty()
	duration(42).pretty()

	// ./listing46.go:17:不能通过指针调用 duration(42)的方法
	// ./listing46.go:17: 不能获取 duration(42)的地址
}

The above code cannot be compiled. duration(42)What is returned is a value, not an address, so the method set of the value only contains methods that use the receiver of the value.

# command-line-arguments
.\listing46.go:19:15: cannot call pointer method pretty on duration

Compilation finished with exit code 1

事实上,编译器并不是总能自动获得一个值的地址,Since this is not always possible 获取一个值的地址, only 值的方法集use is included值接收者实现的方法 . and指针的方法集包括了值接收者和指针接收者

Polymorphism

After understanding the mechanism behind interfaces and method sets, let's finally look at an example showing the polymorphic behavior of interfaces.

// Sample program to show how polymorphic behavior with interfaces.
package main

import (
 "fmt"
)

type notifier interface {
    
    
 notify()
}
type user struct {
    
    
 name  string
 email string
}

func (u *user) notify() {
    
    
 fmt.Printf("Sending user email to %s<%s>\n",
  u.name,
  u.email)
}

type admin struct {
    
    
 name  string
 email string
}

func (a *admin) notify() {
    
    
 fmt.Printf("Sending admin email to %s<%s>\n",
  a.name,
  a.email)
}

func main() {
    
    
 bill := user{
    
    "Bill", "[email protected]"}
 sendNotification(&bill)

 lisa := admin{
    
    "Lisa", "[email protected]"}
 sendNotification(&lisa)
}
func sendNotification(n notifier) {
    
    
 n.notify()
}

If you are familiar with object-oriented programming, this part of things is relatively easy to understand. The difference is that when calling, you need to pay attention to pointer reception and value reception.

If the implementation method is set to value reception, then when calling, it can be called by pointer or value. If the implementation method uses pointer reception, then it can only be called by pointer.

That is, only if 指针接收者one is implemented using . If used to implement an interface, then all types of接口指向那个类型的指针实现对应的接口值接收者值和指针实现对应的接口

Reference to part of the blog post

© The copyright of the reference links in this article belongs to the original author. Please inform us if there is any infringement.


"GO Language Practice"


© 2018-2023 [email protected] , All rights reserved. Keep Attribution-NonCommercial-ShareAlike (CC BY-NC-SA 4.0)

Guess you like

Origin blog.csdn.net/sanhewuyang/article/details/132692518