Go

Golang的特性七(interface接口)----面向对象编程的利器

Royal
2023-08-01 / 0 评论 / 6 阅读 / 正在检测是否收录...

Go 语言虽然不像传统面向对象语言那样拥有类和继承的概念,但它通过接口(interface)提供了一种灵活的方式来实现面向对象编程。本文将深入探讨 Go 语言接口,并结合代码示例,帮助你理解接口的定义、实现、使用以及它在面向对象编程中的应用。

一.接口的定义
Go语言中的接口(interface)是一组方法签名的集合,是一种抽象类型。接口定义了方法,但没有实现,而是由具体的类型(struct)实现这些方法,因此接口是一种实现多态的机制。
Go语言中的接口定义语法如下:

type Animal interface {
  Say() string
  Name() string
}

二. 接口的实现

如果一个类型需要实现 Animal 接口,那么它只需要实现 Say() string 和 Name() string 方法,下面的 Duck 结构体就是接口的一个实现:

type Duck struct {
  Name  string
  Sound string
}
​
func (a *Duck) MySay() string {
  return fmt.Sprintf("My Sound is: %s", a.Sound)
}
​
func (a *Duck) MyName() string {
  return fmt.Sprintf("My Name is: %s", a.Name)
}

上述代码根本就没有 Animal 接口的影子,这是为什么呢?Go 语言中接口的实现都是隐式的,我们只需要实现 MySay() string 和 Name() string 方法就实现了 Animal 接口。

三.接口的使用

func PrintInfo(h Human) {
    fmt.Println(h.Name())
    fmt.Println(h.Say())
}

func main() {
    // 创建 Duck 实例
    duck := &Duck{
        name: "Donald",
        say:  "Quack",
    }

上面的代码定义了一个 PrintInfo() 函数,它接受一个 Human 接口类型的参数。我们可以将任何实现了 Human 接口的类型传递给这个函数,例如 Duck 类型。

完整代码如下:

package main

import "fmt"

// 定义 Human 接口
type Human interface {
    Say() string
    Name() string
}

// 定义 Duck 结构体
type Duck struct {
    name string
    say  string
}

// 实现 Human 接口的 Say 方法
func (d *Duck) Say() string {
    return fmt.Sprintf("My Sound is: %s", d.say)
}

// 实现 Human 接口的 Name 方法
func (d *Duck) Name() string {
    return fmt.Sprintf("My Name is: %s", d.name)
}

// 定义一个函数,接收 Human 接口类型
func PrintInfo(h Human) {
    fmt.Println(h.Name())
    fmt.Println(h.Say())
}

func main() {
    // 创建 Duck 实例
    duck := &Duck{
        name: "Donald",
        say:  "Quack",
    }

    // 调用 PrintInfo 函数,传入 Duck 实例
    PrintInfo(duck)
}

上面代码输出:
My Name is: Donald
My Sound is: Quack

四.接口与面向对象

虽然 Go 语言没有类和继承的概念,但接口可以实现类似的功能。

  • 封装: 接口可以将数据和行为封装在一起,隐藏实现细节。
  • 多态: 接口可以实现多态,使代码更加灵活和可扩展。
  • 组合: 接口可以通过组合其他接口来实现更复杂的行为。

五. 接口的嵌套
Go 语言支持接口的嵌套,可以将多个接口组合成一个新的接口。

type Walker interface {
    Walk() string
}

type Swimmer interface {
    Swim() string
}

type Amphibian interface {
    Walker
    Swimmer
}

上面的代码定义了两个接口 Walker 和 Swimmer,然后将它们组合成一个新的接口 Amphibian。任何实现了 Walker 和 Swimmer 接口的类型都可以被认为是 Amphibian 接口的实现。

六.空接口
空接口 interface{} 不包含任何方法,因此任何类型都实现了空接口。空接口可以用来表示任意类型的值。

func PrintAnything(v interface{}) {
    fmt.Println(v)
}

func main() {
    PrintAnything(42) // 输出: 42
    PrintAnything("Hello") // 输出: Hello
}

七.类型断言
在Go语言中,可以使用类型断言(type assertion)来判断一个接口实例的底层值是什么类型,并将其转换成对应的类型。类型断言的语法如下:

value, ok := interfaceVar.(Type)

使用类型断言

package main

import (
    "fmt"
)

func main() {
    var i interface{}
    i = "hello"

    // 使用类型断言判断 i 的底层值是否为字符串类型
    if s, ok := i.(string); ok {
        fmt.Printf("i is a string: %s\n", s)
    } else {
        fmt.Println("i is not a string")
    }

    // 使用类型断言判断 i 的底层值是否为整数类型
    if n, ok := i.(int); ok {
        fmt.Printf("i is an integer: %d\n", n)
    } else {
        fmt.Println("i is not an integer")
    }
}

八. 指针和结构体接收者
我们经常能看到两种实现接口的接收方式:指针和结构体,看下面缩略代码:

type Animal interface {
  MySay() string
  MyName() string
}
​
type Duck struct {...}
​
//指针方式
func (a *Duck) MySay() string {...}
func (a *Duck) MyName() string {...}
​
//结构体方式
func (a Duck) MySay() string {...}
func (a Duck) MyName() string {...}

因为结构体类型和指针类型是不同的,但是上面两种实现不可以同时存在,Go 语言的编译器会在结构体类型和指针类型都实现一个方法时报错 method redeclared。

实现接口的类型和初始化返回的类型两个维度共组成了四种情况,然而这四种情况不是都能通过编译器的检查:

表头结构体接收者- func (a Duck) MySay()指针接收者 - func (a *Duck) MySay()
结构体指针方式初始化
var Duck Animal = &Duck{}通过检查通过检查
结构体方式初始化
var Duck Animal = Duck{}通过检查不通过

如上表所示,无论上述代码中初始化的变量 是 Duck{} 还是 &Duck{},使用 MySay() 调用方法时都会发生值拷贝:

  • 对于 &Duck{} 来说,这意味着拷贝一个新的 &Duck{} 指针,这个指针与原来的指针指向一个相同并且唯一的结构体,所以编译器可以隐式的对变量解引用(dereference)获取指针指向的结构体;
  • 对于 Duck{} 来说,这意味着 MySay 方法会接受一个全新的 Duck{},因为方法的参数是 *Cat,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体;

总结起来:当我们使用指针实现接口时,只有指针类型的变量才会实现该接口;当我们使用结构体实现接口时,指针类型和结构体类型都会实现该接口。

0

评论

博主关闭了当前页面的评论