接口
是什么?为什么?特点?
在Go语言中,接口定义一系列方法的标签 , 如果某个类型实现了这个接口的所有方法 , 就代表实现了这个接口 , 可以把结构体变量赋给接口变量
为什么?
多态:使用相同的操作来处理不同类型的对象
实现面向对象的多态,通过接口操作多个类型。
特点?
隐式接口。不要求类型显示地声明实现了某个接口,只要实现了相关地方法即可,因为编译器能检测到,没有虚函数,继承。
接口什么情况等于nil?
接口的动态类型和动态值都为nil时,接口等于nil。
接口_声明和赋值(多态)
Go
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 实现接口:圆形
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 实现接口:矩形
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2*r.Width + 2*r.Height
}
func main() {
// 创建圆形和矩形对象
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 3, Height: 4}
fmt.Println(circle.Area(), circle.Perimeter())
fmt.Println(rectangle.Area(), rectangle.Perimeter())
}
实现fmt包中的Stringer接口
Go
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
func main() {
person := Person{Name: "Alice", Age: 30}
fmt.Println(person) // 这里会自动调用 String() 方法输出 "Alice is 30 years old"
}
接口_比较
在Go语言中,一个接口被认为是nil,其类型和值都必须是nil。
运行时候,接口被实现为一堆指针,一个指向底层类型,一个指向基础值。
Go
// 接口值在动态类型和动态值都等于nil时等于nil
type Coder interface {
code()
}
type Gopher struct {
name string
}
func (g Gopher) code() {
fmt.Printf("%s is coding\n", g.name)
}
func main() {
var c Coder
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
var g *Gopher
fmt.Println(g == nil)
c = g
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
}
Go
func main() {
var s *string
fmt.Println(s == nil)
var i interface{}
var i2 interface{}
fmt.Println(i == nil)
fmt.Println(i == i2)
fmt.Println(s == i)
}
Go
/*
在 Go 语言中,当你尝试在一个 nil 接口值上调用方法时,会引发运行时错误(panic)。这种情况通常会发生在接口的底层值为 nil 的情况下。
*/
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
var s Shape
var c *Circle // c 是一个指向 Circle 类型的指针变量,但尚未初始化
s = c // 将一个指向 Circle 的 nil 指针赋值给接口 s
fmt.Println(c == nil)
fmt.Println(s == nil)
fmt.Println(s.Area()) // 这里会引发 panic,因为 s 是一个 nil 接口值
}
接口转换
定义一个MyError结构体,error接口。
Process函数返回一个error接口。
Go
// 隐含的类型转换.动态类型不一致
type MyError struct {
}
func (e MyError) Error() string {
return "MyError"
}
func Process() error {
return MyError{}
}
func main() {
e := Process()
fmt.Println(e == nil)
}
接口_接口在函数中传递(依赖注入)
在 Go 中,依赖注入是一种设计模式,用于将对象的依赖项(或依赖关系)从对象本身中解耦。利用接口和依赖注入,可以在运行时传递依赖项,允许在不修改现有代码的情况下替换具体的实现。
在 Go 语言中,可以在函数中传递接口。这种方法使得函数能够接受满足特定接口定义的任何类型。通过接口,可以实现对不同类型的对象进行抽象处理。
Go
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 实现接口:圆形
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 实现接口:矩形
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2*r.Width + 2*r.Height
}
func printShapeInfo(s Shape) {
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
func main() {
// 创建圆形和矩形对象
circle := Circle{Radius: 5}
rectangle := Rectangle{Width: 3, Height: 4}
printShapeInfo(circle)
printShapeInfo(rectangle)
}
接口_接口在函数中返回
这种方法允许函数返回某个结构体或类型的实例,并以接口的方式暴露出来,这样可以隐藏具体类型的实现细节。
Go
package main
import (
"fmt"
)
// 定义一个接口 Shape
type Shape interface {
getName() string
}
// 定义 Circle 结构体,并实现 Shape 接口的 getName 方法
type Circle struct {
radius float64
}
func (c Circle) getName() string {
return "Circle"
}
// 创建一个函数 createCircle 返回 Shape 接口
func createCircle() Shape {
return Circle{radius: 5}
}
func main() {
circle := createCircle() // 返回的是 Shape 接口,指向 Circle 结构体实例
// 通过 Shape 接口调用 getName() 方法
fmt.Println("Circle's name:", circle.getName())
}
接口_结构体内嵌接口(委托模式)
结构体内嵌接口
委托模式这意味着一个对象将其特定的职责委托给另一个对象来处理。
在 Go 中,结构体嵌入结构体或者方法的方式可以实现委托。
Go
package main
import "fmt"
// Animal 接口定义了动物发出声音的方法
type Animal interface {
MakeSound() string
}
// Dog 结构体代表狗
type Dog struct{}
func (d Dog) MakeSound() string {
return "Woof!"
}
// Cat 结构体代表猫
type Cat struct{}
func (c Cat) MakeSound() string {
return "Meow!"
}
// Pet 结构体代表宠物,委托 Animal 接口
type Pet struct {
animal Animal
}
func (p Pet) MakeSound() string {
return p.animal.MakeSound()
}
func main() {
dog := Dog{}
cat := Cat{}
petDog := Pet{animal: dog}
petCat := Pet{animal: cat}
fmt.Println("Dog says:", petDog.MakeSound()) // 委托给狗对象
fmt.Println("Cat says:", petCat.MakeSound()) // 委托给猫对象
}
接口_接口内嵌接口
在 Go 中,接口可以嵌入到其他接口,形成一种接口组合的结构。这种方式可以用于构建更大、更复杂的接口,将多个小的接口组合成一个更大的接口。
Go
// Sound 接口定义了动物发出声音的方法
type Sound interface {
Sound() string
}
// Move 接口定义了动物的移动方法
type Move interface {
Move() string
}
// SoundMove 接口内嵌了 Sound 和 Move 接口
type SoundMove interface {
Sound
Move
}
type Dog struct{}
func (d Dog) Sound() string {
return "Woof!"
}
func (d Dog) Move() string {
return "Running"
}
func main() {
var soundMove SoundMove
soundMove = Dog{}
fmt.Println(soundMove.Sound())
fmt.Println(soundMove.Move())
}
接口_函数类型实现接口
Go
// 定义一个接口
type Greeter interface {
Greet() string
}
// 定义一个函数类型
type GreetingFunc func() string
// 实现接口的方法
func (g GreetingFunc) Greet() string {
return g()
}
func main() {
// 定义一个函数,返回字符串 "Hello, World!"
hello := func() string {
return "Hello, World!"
}
// 将函数转换为 GreetingFunc 类型
greetingFunc := GreetingFunc(hello)
// 将函数类型变量传递给接口类型
var greeter Greeter = greetingFunc
// 调用接口方法
fmt.Println(greeter.Greet()) // 输出 "Hello, World!"
}
接口_接受接口,返回结构体
接口最佳实践?
error规则是一个例外情况,需要返回不同的error实现。
接口_接口实现
实现接口io.Reader
Go
package main
import (
"fmt"
"io"
"io/ioutil"
"log"
)
// MyReader 是自定义的读取器类型
type MyReader struct {
data []byte // 存储要读取的数据
current int // 当前读取位置
}
// NewMyReader 创建一个 MyReader 实例
func NewMyReader(data []byte) *MyReader {
return &MyReader{
data: data,
current: 0,
}
}
// Read 实现了 Reader 接口的 Read 方法
func (r *MyReader) Read(p []byte) (int, error) {
if r.current >= len(r.data) { // 如果已经读取完所有数据
return 0, io.EOF // 返回读取字节数为 0,同时返回 io.EOF 表示已经到达文件末尾
}
n := copy(p, r.data[r.current:]) // 将数据从 r.data 复制到 p 中,返回实际复制的字节数
r.current += n // 更新当前读取位置
return n, nil // 返回复制的字节数和没有错误
}
func main() {
data := []byte("Hello, World!")
reader := NewMyReader(data) // 创建 MyReader 实例,用于读取 data
b, err := ioutil.ReadAll(reader) // 使用 ioutil 包的 ReadAll 函数读取全部数据
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", b)
}
实现接口io.Write
ioutil.ReadAll(reader) 相当于通用方法,是用于从实现了 io.Reader 接口的对象中读取所有数据的操作。
这种通用的方法可以用于各种情况,例如从文件、网络连接、HTTP 响应等读取数据。无论数据源是什么,只要实现了 io.Reader 接口,就可以使用 ioutil.ReadAll 来读取数据。这种做法使得代码编写更加灵活和通用,无需针对不同类型的数据源编写不同的读取逻辑。
Go
package main
import (
"fmt"
"io"
)
type MyWriter struct {
data []byte
}
func NewMyWriter() *MyWriter {
return &MyWriter{}
}
func (w *MyWriter) Write(p []byte) (int, error) {
w.data = append(w.data, p...)
return len(p), nil
}
func main() {
writer := NewMyWriter()
writer.Write([]byte("Hello, "))
writer.Write([]byte("World!"))
fmt.Println("Data:", string(writer.data))
io.WriteString(writer, "Hello World")
fmt.Printf("%v\n", string(writer.data))
}
实现接口error
Go
package main
import "fmt"
type User struct {
}
func (u User) Error() string {
return "错误"
}
func main() {
var e error
e = User{}
fmt.Println(e.Error())
}