您的位置 首页 java

如何在Go中使用接口

介绍

编写灵活的、可重用的和模块化的代码对于开发通用程序是至关重要的。以这种方式工作可以避免在多个位置进行相同的更改,从而确保代码更容易维护。如何做到这一点因语言而异。例如,继承是在 Java c++ 、c#等语言中使用的一种常见方法。

开发人员还可以通过组合实现相同的设计目标。复合是将对象或数据类型组合成更复杂的类型的一种方法。这是 Go 用来促进代码重用、模块化和灵活性的方法。Go中的接口提供了一种组织复杂组合的方法,学习如何使用它们将允许您创建通用的、可重用的代码。

在本文中,我们将学习如何组合具有常见行为的自定义类型,这将允许我们重用代码。我们还将学习如何为我们实现自定义类型实现接口,并使用从另一个包中定义的接口。

定义一个接口行为

组合的核心实现之一是接口的使用。接口定义了类型的行为。Go标准库中最常用的接口之一是fmt.Stringer接口:

type Stringer interface {

String() string

}

第一行代码定义了一个名为Stringer的类型,然后声明它是一个接口。就像定义一个 结构体 一样,Go使用大括号({})来包围接口的定义。与定义结构相比,我们只定义了接口的行为:也就是“这种类型能做什么”。

对于Stringer接口,唯一的行为是String()方法。该方法不带参数,返回一个 字符串

接下来,让我们看一些具有fmt.Stringer的代码行为:

package main

import “fmt”

type Article struct {

Title string

Author string

}

func (a Article) String() string {

return fmt.Sprintf(“The %q article was written by %s.”, a.Title, a.Author)

}

func main() {

a := Article{

Title: “Understanding Interfaces in Go”,

Author: “Kobe Bryant”,

}

fmt.Println(a.String())

}

我们要做的第一件事是创建一个名为Article的新类型。该类型有一个Title和一个Author字段,两者都是字符串数据类型。

接下来,我们在Article类型上定义一个名为String的方法。String方法将返回一个表示Article类型的字符串。

然后,在我们的主函数中,我们创建了一个Article类型的实例,并将其赋值给名为a的变量。我们为Title字段提供“Understanding Interfaces in Go”的值,为Author字段提供“Kobe Bryant”的值。

最后,通过调用fmt输出String方法Println,并传入a.String()方法调用的结果,最终输出:

Output

The “Understanding Interfaces in Go” article was written by Kobe Bryant.

到目前为止,我们还没有使用接口,但是我们确实创建了一个具有行为的类型。该行为匹配fmt.Stringer接口。接下来,让我们看看如何使用该行为来提高代码的可重用性。

定义一个接口

现在我们已经用所需的行为定义了类型,我们可以看看如何使用该行为。

然而,在我们这样做之前,让我们看看如果我们想从函数中的Article类型调用String方法,我们需要做什么。

package main

import “fmt”

type Article struct {

Title string

Author String

}

func (a Article) String() string {

return fmt.Sprintf(“The %q article was written by %s.”, a.Title, a.Author)

}

func main() {

a := Article{

Title: “Understanding Interfaces in Go”,

Author: “Sammy Shark”,

}

Print(a)

}

func Print(a Article) {

fmt.Println(a.String())

}

在这段代码中,我们添加了一个名为Print的新函数,该函数接受一个Article作为参数。注意,Print函数所做的唯一一件事就是调用String方法。因此,我们可以定义一个接口来传递给函数:

package main

import “fmt”

type Article struct {

Title string

Author string

}

func (a Article) String() string {

return fmt.Sprintf(“The %q article was written by %s.”, a.Title, a.Author)

}

type Stringer interface {

String() string

}

func main() {

a := Article{

Title: “Understanding Interfaces in Go”,

Author: “Sammy Shark”,

}

Print(a)

}

func Print(s Stringer) {

fmt.Println(s.String())

}

这里我们创建了一个名为Stringer的接口:


type Stringer interface {

String() string

}

Stringer接口只有一个返回字符串的方法String()。接口方法是Go中作用域为特定类型的特殊函数。与函数不同,方法只能从定义它的类型的实例调用。

然后更新Print方法的签名,使其接受一个接口Stringer,而不是一个具体的Article类型。因为编译器知道Stringer接口定义了String方法,所以它只接受同样具有String方法的类型。

现在,我们可以对满足Stringer接口的任何内容使用Print方法。让我们创建另一个类型来演示:

package main

import “fmt”

type Article struct {

Title string

Author string

}

func (a Article) String() string {

return fmt.Sprintf(“The %q article was written by %s.”, a.Title, a.Author)

}

type Book struct {

Title string

Author string

Pages int

}

func (b Book) String() string {

return fmt.Sprintf(“The %q book was written by %s.”, b.Title, b.Author)

}

type Stringer interface {

String() string

}

func main() {

a := Article{

Title: “Understanding Interfaces in Go”,

Author: “Kobe Bryant”,

}

Print(a)

b := Book{

Title: “All About Go”,

Author: “Jenny Dolphin”,

Pages: 25,

}

Print(b)

}

func Print(s Stringer) {

fmt.Println(s.String())

}

现在我们添加了第二种类型,称为Book。它还定义了String方法。这意味着它也满足Stringer接口。因此,我们也可以将它发送给我们的Print函数:

Output

The “Understanding Interfaces in Go” article was written by Kobe Bryant.

The “All About Go” book was written by Jenny Dolphin. It has 25 pages.

到目前为止,我们已经演示了如何只使用一个接口。但是,一个接口可以定义多个行为。接下来,我们将看到如何通过声明更多的方法使接口更通用。

一个接口中的多种行为

编写Go代码的核心之一是编写小而简洁的类型,并将它们组合成更大、更复杂的类型。组合接口时也是如此。为了了解如何构建接口,我们首先只定义一个接口。我们将定义两个形状,圆形和方形,它们都将定义一个名为Area的方法。这个方法将返回它们各自形状的几何面积:

package main

import (

“fmt”

“math”

)

type Circle struct {

Radius float64

}

func (c Circle) Area() float64 {

return math.Pi * math.Pow(c.Radius, 2)

}

type Square struct {

Width float64

Height float64

}

func (s Square) Area() float64 {

return s.Width * s.Height

}

type Sizer interface {

Area() float64

}

func main() {

c := Circle{Radius: 10}

s := Square{Height: 10, Width: 5}

l := Less(c, s)

fmt.Printf(“%+v is the smallestn”, l)

}

func Less(s1, s2 Sizer) Sizer {

if s1.Area() < s2.Area() {

return s1

}

return s2

}

因为每种类型都声明了Area方法,所以我们可以创建一个定义该行为的接口。我们创建了以下Sizer接口:

type Sizer interface {

Area() float64

}

然后定义一个名为Less的函数,该函数接受两个size值,并返回最小的size值:

func Less(s1, s2 Sizer) Sizer {

if s1.Area() < s2.Area() {

return s1

}

return s2

}

请注意,我们不仅接受两个参数作为类型Sizer,而且还将结果作为接口Sizer返回。这意味着我们返回的不再是正方形或圆形,而是Sizer的接口。

最后,我们打印出面积最小的区域:

Output

{Width:5 Height:10} is the smallest

接下来,让我们为每种类型添加另一个行为。这次我们将添加返回字符串的String()方法。这将满足fmt.Stringer接口:

package main

import (

“fmt”

“math”

)

type Circle struct {

Radius float64

}

func (c Circle) Area() float64 {

return math.Pi * math.Pow(c.Radius, 2)

}

func (c Circle) String() string {

return fmt.Sprintf(“Circle {Radius: %.2f}”, c.Radius)

}

type Square struct {

Width float64

Height float64

}

func (s Square) Area() float64 {

return s.Width * s.Height

}

func (s Square) String() string {

return fmt.Sprintf(“Square {Width: %.2f, Height: %.2f}”, s.Width, s.Height)

}

type Sizer interface {

Area() float64

}

type Shaper interface {

Sizer

fmt.Stringer

}

func main() {

c := Circle{Radius: 10}

PrintArea (c)

s := Square{Height: 10, Width: 5}

PrintArea (s)

l := Less(c, s)

fmt.Printf(“%v is the smallestn”, l)

}

func Less(s1, s2 Sizer) Sizer {

if s1.Area() < s2.Area() {

return s1

}

return s2

}

func PrintArea(s Shaper) {

fmt.Printf(“area of %s is %.2fn”, s.String(), s.Area())

}

因为Circle和Square类型都实现了Area和String方法,我们现在可以创建另一个接口来描述更广泛的行为集。为此,我们将创建一个名为Shaper的接口,把Sizer接口和fmt.Stringer接口组合在一起:

type Shaper interface {

Sizer

fmt.Stringer

}

现在我们可以创建一个名为PrintArea的函数,它接受Shaper作为参数。这意味着我们可以同时调用Area和String方法:

func PrintArea(s Shaper) {

fmt.Printf(“area of %s is %.2fn”, s.String(), s.Area())

}

输出结果:

Output

area of Circle {Radius: 10.00} is 314.16

area of Square {Width: 5.00, Height: 10.00} is 50.00

Square {Width: 5.00, Height: 10.00} is the smallest

现在我们已经看到了如何创建较小的接口,并根据需要将其构建为较大的接口。虽然我们可以从更大的接口开始,并将其传递给所有函数,但最佳实践是只向需要的函数发送最小的接口。这通常会产生更清晰的代码,因为任何接受特定较小接口的程序都只打算使用已定义的行为。

例如,如果我们将Shaper传递给Less函数,我们可以假设它将调用Area和String方法。然而,由于我们只打算调用Area方法,传递Sizer接口使得Less函数变得很清楚,因为我们知道,我们只能调用传递给它的任何参数的Area方法。

总结

我们已经了解了如何创建较小的接口并将其构建为较大的接口,从而共享函数或方法所需的内容。我们还了解到可以用其他接口组合我们的接口,包括那些从其他包定义的接口,而不仅仅是从我们自己定义的包。

文章来源:智云一二三科技

文章标题:如何在Go中使用接口

文章地址:https://www.zhihuclub.com/174884.shtml

关于作者: 智云科技

热门文章

网站地图