Go 接口使用

个人学习笔记

接口作用

1. 实现多态

多态允许不同的类型通过实现相同的接口,以统一的方式进行处理。这使得代码更加灵活和可扩展,提高了代码的复用性。

示例代码

package main

import (
    "fmt"
)

// 定义一个接口
type Speaker interface {
    Speak() string
}

// 定义一个Dog结构体
type Dog struct{}

// 实现Speaker接口的Speak方法
func (d Dog) Speak() string {
    return "Woof!"
}

// 定义一个Cat结构体
type Cat struct{}

// 实现Speaker接口的Speak方法
func (c Cat) Speak() string {
    return "Meow!"
}

// 一个接受Speaker接口类型参数的函数
func makeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    dog := Dog{}
    cat := Cat{}

    makeSound(dog)
    makeSound(cat)
}
代码逐行解释
type Speaker interface {
  • type:这是 Go 语言中用于定义新类型的关键字。

  • Speaker:这是新定义的接口的名称。按照 Go 语言的命名规范,接口名通常使用描述性的名称,并且首字母大写表示该接口是可导出的(可以被其他包使用)。

  • interface:该关键字用于声明一个接口类型。

      Speak() string
    
  • Speak:这是接口中定义的方法的名称。

  • ():这对括号表明 Speak 是一个方法,而不是一个字段或其他类型的成员。在 Go 语言中,方法是与特定类型关联的函数,() 用于指定方法的参数列表。这里 () 为空,表示该方法不接受任何参数。

  • string:这是 Speak 方法的返回值类型,意味着该方法会返回一个字符串。

    }

这是接口定义的结束符号。

为什么 Speak 后面要加 ()

在 Go 语言里,方法是与特定类型关联的函数,而函数的定义需要明确其参数列表和返回值类型。() 是用来表示参数列表的,即使方法没有参数,也必须使用 () 来表明这是一个方法的定义。

如果 Speak 后面不加 (),代码就会变成这样:

type Speaker interface {
    Speak string
}

此时,Speak 会被视为接口中的一个字段,而不是一个方法。字段是用于存储数据的,而方法是用于执行操作的,两者的用途完全不同。

解释 :在上述代码中,DogCat结构体都实现了Speaker接口的Speak方法。makeSound函数接受一个Speaker接口类型的参数,因此可以传入DogCat类型的对象,以统一的方式调用它们的Speak方法,实现了多态。

2. 解耦代码

接口可以将抽象和实现分离,降低代码之间的耦合度。调用方只需要依赖接口,而不需要依赖具体的实现类型,使得代码更加易于维护和扩展。

示例代码

package main

import (
    "fmt"
)

// 定义一个接口
type Storage interface {
    Save(data string)
    Load() string
}

// 定义一个FileStorage结构体
type FileStorage struct{}

// 实现Storage接口的Save方法
func (fs FileStorage) Save(data string) {
    fmt.Printf("Saving data '%s' to file.\n", data)
}

// 实现Storage接口的Load方法
func (fs FileStorage) Load() string {
    return "Data loaded from file."
}

// 定义一个MemoryStorage结构体
type MemoryStorage struct{}

// 实现Storage接口的Save方法
func (ms MemoryStorage) Save(data string) {
    fmt.Printf("Saving data '%s' to memory.\n", data)
}

// 实现Storage接口的Load方法
func (ms MemoryStorage) Load() string {
    return "Data loaded from memory."
}

// 一个使用Storage接口的函数
func processData(s Storage) {
    s.Save("Sample data")
    result := s.Load()
    fmt.Println(result)
}

func main() {
    fileStorage := FileStorage{}
    memoryStorage := MemoryStorage{}

    processData(fileStorage)
    processData(memoryStorage)
}

解释processData函数依赖于Storage接口,而不是具体的存储实现类型(如FileStorageMemoryStorage)。这样,当需要更换存储方式时,只需要实现Storage接口的新类型,而不需要修改processData函数的代码,降低了代码的耦合度。

3. 提供抽象层

接口可以为一组相关的操作提供一个抽象层,隐藏具体的实现细节,使得代码更加简洁和易于理解。

示例代码

package main

import (
    "fmt"
)

// 定义一个接口
type Database interface {
    Connect()
    Query(query string) []string
    Close()
}

// 一个使用Database接口的函数
func performDatabaseOperations(db Database) {
    db.Connect()
    results := db.Query("SELECT * FROM users")
    fmt.Println(results)
    db.Close()
}

解释Database接口为数据库操作提供了一个抽象层,调用方只需要知道如何使用这些抽象的方法,而不需要了解具体数据库的连接、查询和关闭的实现细节。

4. 方便单元测试

在单元测试中,接口可以用于创建模拟对象,方便对代码进行隔离测试。

示例代码

package main

import (
    "fmt"
    "testing"
)

// 定义一个接口
type Calculator interface {
    Add(a, b int) int
}

// 定义一个具体的实现结构体
type RealCalculator struct{}

// 实现Calculator接口的Add方法
func (rc RealCalculator) Add(a, b int) int {
    return a + b
}

// 一个使用Calculator接口的函数
func calculateSum(c Calculator, a, b int) int {
    return c.Add(a, b)
}

// 定义一个模拟对象
type MockCalculator struct{}

// 实现Calculator接口的Add方法
func (mc MockCalculator) Add(a, b int) int {
    return 100 // 模拟返回值
}

func TestCalculateSum(t *testing.T) {
    mockCalc := MockCalculator{}
    result := calculateSum(mockCalc, 1, 2)
    if result != 100 {
        t.Errorf("Expected 100, got %d", result)
    }
}

func main() {
    realCalc := RealCalculator{}
    sum := calculateSum(realCalc, 3, 4)
    fmt.Println(sum)
    testing.Main(func(pat, str string) (bool, error) { return true, nil }, []testing.InternalTest{
        {"TestCalculateSum", TestCalculateSum},
    }, nil, nil)
}

解释 :在单元测试中,通过创建MockCalculator结构体实现Calculator接口,可以模拟Add方法的返回值,从而对calculateSum函数进行隔离测试,而不需要依赖真实的计算逻辑。

案例代码解释

  1. 接口定义 :定义了一个Shape接口,包含AreaPerimeter两个方法。
  2. 结构体定义 :定义了RectangleCircle两个结构体。
  3. 接口实现 :为RectangleCircle结构体分别实现了AreaPerimeter方法,从而实现了Shape接口。
  4. 接口使用 :定义了一个PrintShapeInfo函数,接受一个Shape接口类型的参数,在main函数中分别传入RectangleCircle对象调用该函数。
Go 复制代码
package main

import (
    "fmt"
)

// 定义一个接口
type Shape interface {
    Area() float64
    Perimeter() float64
}

// 定义一个矩形结构体
type Rectangle struct {
    Width  float64
    Height float64
}

// 实现Shape接口的Area方法
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 实现Shape接口的Perimeter方法
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// 定义一个圆形结构体
type Circle struct {
    Radius float64
}

// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

// 实现Shape接口的Perimeter方法
func (c Circle) Perimeter() float64 {
    return 2 * 3.14 * c.Radius
}

// 一个接受Shape接口类型参数的函数
func PrintShapeInfo(s Shape) {
    fmt.Printf("Area: %.2f\n", s.Area())
    fmt.Printf("Perimeter: %.2f\n", s.Perimeter())
}

func main() {
    // 创建一个矩形对象
    rect := Rectangle{Width: 5, Height: 3}
    // 创建一个圆形对象
    circle := Circle{Radius: 2}

    // 调用PrintShapeInfo函数,传入矩形对象
    fmt.Println("Rectangle Info:")
    PrintShapeInfo(rect)

    // 调用PrintShapeInfo函数,传入圆形对象
    fmt.Println("\nCircle Info:")
    PrintShapeInfo(circle)
}
相关推荐
2302_7995257410 分钟前
【go语言】——方法集
开发语言·后端·golang
源码姑娘17 分钟前
基于SpringBoot的校园二手交易平台(源码+论文+部署教程)
java·spring boot·后端·毕业设计
非 白22 分钟前
【Java 后端】Restful API 接口
java·开发语言·restful
rider18923 分钟前
Java多线程及线程变量学习:从熟悉到实战(下)
java·开发语言·学习
元亓亓亓23 分钟前
java后端开发day26--常用API(一)
java·开发语言
JP-Destiny25 分钟前
后端-Java虚拟机
java·开发语言·jvm
m0_5485030327 分钟前
基于Springboot高校社团管理系统【附源码+文档】
java·spring boot·后端
lzz的编码时刻28 分钟前
Spring Security简介与使用
java·后端·spring
葡萄_成熟时_31 分钟前
JavaWeb后端基础(4)
java·开发语言·数据库·maven·web
Hello.Reader44 分钟前
深入解析 Rust 异步编程中的 Traits
开发语言·后端·rust