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
Golang
Inside 多态
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 io
the streaming interface implemented in the package. io
The package provides a set of well-constructed interfaces and functions to allow code to easily support streaming data processing.
只要实现两个接口
, you can take advantage of io
all 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.Response
The type contains a Body
field named which is a io.ReadCloser
value of interface type
io.Copy
The second parameter of the function accepts a io.Reader
value of interface type, which represents the source of data inflow. Body
The field implements io.Reader
the interface
io.Copy
The first parameter is the target to copy to. This parameter must be an implemented io.Writer
interface. os
A special value in the package Stdout
indicates the standard output device, which has implemented io.Writer
the 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.Fprintf
The function accepts an io.Writer
interface value of type as its first parameter, bytes.Buffer
a pointer of type implements io.Writer
interface, and bytes.Buffer
a pointer of type also implements io.Reader
interface, again using io.Copy
the 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.Writer
and io.Reader
interface
type Writer interface {
Write(p []byte) (n int, err error)
}
type Reader interface {
Read(p []byte) (n int, err error)
}
bytes.Buffer
The 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
GO
The 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 user
the 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.
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.
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
=============================================
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:user
does not implement notifier (the notify method is declared using a pointer receiver)
user
To 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)