likes
comments
collection
share

Go - 基础语法分析 - 接口

作者站长头像
站长
· 阅读数 45

Go - 基础语法 - 接口

注: 文章仅提供对标题内容的直接讨论,并不提供间接相关知识点的讨论。这些间接涉及的知识点后续可能会更新文章。如果喜欢我的描述方式,欢迎评论区提问或直接与我讨论,并请关注我留意我的更新。或者也可以直接去找搜索引擎求知。

接口的基本解释:

       接口是一种抽象类型(方法签名的集合),它定义了一组方法签名,但不包含这些方法的实现。任何实现了这些方法的具体类型(例如结构体)都被认为实现了该接口。接口使得代码更加灵活和可扩展,允许不同的类型通过实现相同的接口来进行操作。

而接口的常见用途有:入参、返回值、结构体字段。

接口的思想:

        在Go语言中,接口是将对象(结构体)的数个行为(方法)统合成一种能力的描述,而拥有这种能力对应的所有行为(可以实现对应所有方法),就是实现了对应接口。

接口的使用:

       在写代码时,应该在某个入参项确实需要一个灵活化的入参或返回值时,才需要根据此处入参应该具备的能力,从而生成对应的接口,而不是预先我认为应该有一个什么样的接口。因为能力的描述应该由需求提出而不是预置,否则会容易造成接口污染 。具体原则如下:

接口原则:

  • 基于需求定义:只有在需要灵活参数(入参、返回值、结构体字段)的概念时,才定义接口,并且接口中只包含实际需要的方法。
  • 追求小而专一:一个接口应该只包含少量的方法,在官方包中,一个接口中所含方法的平均值仅为2。且这些方法应具有高度相关性。方便接口的实现并保证泛用性
  • 仅对行为抽象:接口的定义应该集中在描述一种能力,并非绑定具体的实现细节。

接口污染:

       接口污染是指在接口中添加了过多的方法,使得接口变得臃肿和复杂,从而影响代码的可维护性和灵活性。定义一个过大的接口会使得实现该接口的结构体需要实现很多不必要的方法,增加了不必要的负担。

案例

package main

import (
    "fmt"
)

// 定义一个接口(一种能力的定义,可能是多个子能力的集合也可能是单个)
type Speaker interface {
    Speak() string
}

// 定义一个结构体(一个类)
type Person struct {
    name string
}

// Speak 这里的含义就是描述了Person这个类,拥有Speaker的能力
// 而Speak就是该能力的子能力,Person实现这个子能力所进行的行为就是方法中的逻辑部分
func (p Person) Speak() string {
    return "Hello, my name is " + p.name
}

func main() {
    var s Speaker
    p := Person{name: "Oreki"}
    s = p
    fmt.Println(s.Speak()) // 输出: Hello, my name is Oreki
}

接口的意义:

       接口的出现增加了代码的行数和抽象程度,那么该语法的出现必然有其意义:

优点

  • 灵活性(多态)
    • 使用接口可以使你的函数适应多种不同类型,只要这些类型实现了相应的接口。这种灵活性使得你的代码更具适应性和可复用性。
  • 可扩展性
    • 通过使用接口,你可以在不修改现有代码的情况下引入新的实现。例如,你可以轻松地为现有接口添加新的实现,或者在不同的上下文中使用不同的实现。
  • 松耦合
    • 接口定义了能力(方法或方法组),而不关心具体的行为(方法实现)。这使得代码模块之间更加独立,降低了耦合度,提高了代码的可维护性和测试性。
  • 单一职责原则
    • 通过将行为抽象到接口中,可以使得每个类型专注于自己的职责,从而遵循单一职责原则。

注意事项

  • 可读性
    • 虽然使用接口和结构体作为参数可以提高灵活性,但如果使用不当,可能会降低代码的可读性。通过添加详细的注释和合理的命名,可以在一定程度上弥补这一点。
  • 接口设计
    • 在设计接口时,应确保接口尽可能小而专一。避免设计过于庞大的接口,以免引入接口污染的问题。
  • 避免过度设计
    • 虽然接口的使用可以提高灵活性,但应避免过度设计。只有在确实需要灵活性和可扩展性时,才使用接口进行抽象。

示例

假设我们在开发一个支付系统,我们希望系统能够处理多种支付方式。我们可以定义一个 PaymentProcessor 接口,并让不同的支付方式实现这个接口。

package main

import "fmt"

// PaymentAmount 表示支付金额和币种
type PaymentAmount struct {
    Amount     float64
    Currency   string
    ExchangeRate float64  // 汇率
    Timestamp  int64     // 支付时间
    Notes      string    // 支付备注
}


// PaymentProcessor 定义了支付处理的行为
type PaymentProcessor interface {
    ProcessPayment(amount PaymentAmount) error
}

// AliPay 实现了 PaymentProcessor 接口
type AliPay struct {
    CardNumber string
}

func (cc AliPay) ProcessPayment(amount PaymentAmount) error {
    fmt.Printf("Processing AliPay payment of %.2f %s\n", amount.Amount, amount.Currency)
    return nil
}

// UnionPay 实现了 PaymentProcessor 接口
type UnionPay struct {
    Email string
}

func (pp UnionPay) ProcessPayment(amount PaymentAmount) error {
    fmt.Printf("Processing UnionPay payment of %.2f %s\n", amount.Amount, amount.Currency)
    return nil
}

// ProcessPayments 处理支付的函数
// 使用 PaymentProcessor 接口和 PaymentAmount 结构体作为参数
func ProcessPayments(processors []PaymentProcessor, amount PaymentAmount) {
    for _, processor := range processors {
        if err := processor.ProcessPayment(amount); err != nil {
            fmt.Println("Error processing payment:", err)
        }
    }
}

func main() {
    processors := []PaymentProcessor{
        AliPay{CardNumber: "4111111111111111"},
        UnionPay{Email: "user@example.com"},
    }

    amount := PaymentAmount{Amount: 99.99, Currency: "RMB"}
    ProcessPayments(processors, amount)
}

       这里 PaymentProcessor 接口定义了一个处理支付的行为。具体的支付方式(如 AliPay 和 UnionPay)实现了这个接口。ProcessPayments 函数使用 PaymentProcessor 接口作为参数,这使得它能够处理任何实现了 PaymentProcessor 接口的支付方式,从而提高了支付系统的灵活性和可扩展性。 同时结构体的使用也让入参之一的金额部分更加灵活,强大了支付行为这个类的功能性。

总结:

       善用接口,可以让代码更加灵活、可扩展、易于维护。但不要提前设定更不要滥用接口,同时每个接口都应该有完善的备注,表明它到底描述了一种什么样的能力以减少代码抽象带来的阅读难度。

个人: 如果阅读我的文章对你有所帮助,切实的解决了你对该知识的大部分疑问,又或对我的描述方式认可,欢迎阅读我的 思想篇专栏(TODO)。这个专栏并不直接更新技术类文章,而是对一些思维能力做梳理,方便提升思考和认知等能力。思维能力的提升对于你的技术、你的人生都是大有裨益,希望更多的人可以共同深度思考!总之和谐评论,点赞有益身心健康,谢谢!

转载自:https://juejin.cn/post/7387226491866169396
评论
请登录