golang接口的使用场景_如何在Go中使用接口

golang接口的使用场景

介绍 (Introduction)

Writing flexible, reusable, and modular code is vital for developing versatile programs. Working in this way ensures code is easier to maintain by avoiding the need to make the same change in multiple places. How you accomplish this varies from language to language. For instance, inheritance is a common approach that is used in languages such as Java, C++, C#, and more.

编写灵活,可重用和模块化的代码对于开发通用程序至关重要。 通过这种方式工作,避免了在多个位置进行相同更改的需要,从而确保了代码的维护更加容易。 您完成此操作的方式因语言而异。 例如, 继承是Java,C ++,C#等语言使用的一种常见方法。

Developers can also attain those same design goals through composition. Composition is a way to combine objects or data types into more complex ones. This is the approach that Go uses to promote code reuse, modularity, and flexibility. Interfaces in Go provide a method of organizing complex compositions, and learning how to use them will allow you to create common, reusable code.

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

In this article, we will learn how to compose custom types that have common behaviors, which will allow us to reuse our code. We’ll also learn how to implement interfaces for our own custom types that will satisfy interfaces defined from another package.

在本文中,我们将学习如何编写具有共同行为的自定义类型,这将使​​我们能够重用我们的代码。 我们还将学习如何为自己的自定义类型实现接口,这些接口将满足从另一个包定义的接口。

定义行为 (Defining a Behavior)

One of the core implementations of composition is the use of interfaces. An interface defines a behavior of a type. One of the most commonly used interfaces in the Go standard library is the fmt.Stringer interface:

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

type Stringer interface {
    String() string
}

The first line of code defines a type called Stringer. It then states that it is an interface. Just like defining a struct, Go uses curly braces ({}) to surround the definition of the interface. In comparison to defining structs, we only define the interface’s behavior; that is, “what can this type do”.

代码的第一行定义了一个称为Stringertype 。 然后声明它是一个interface 。 就像定义一个结构一样,Go使用花括号( {} )来包围接口的定义。 与定义结构相比,我们仅定义接口的行为 ; 也就是说,“这种类型可以做什么”。

In the case of the Stringer interface, the only behavior is the String() method. The method takes no arguments and returns a string.

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

Next, let’s look at some code that has the fmt.Stringer behavior:

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

main.go
main.go
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",
    }
    fmt.Println(a.String())
}

The first thing we do is create a new type called Article. This type has a Title and an Author field and both are of the string data type:

我们要做的第一件事是创建一个称为Article的新类型。 此类型具有TitleAuthor字段,并且均为字符串数据类型 :

main.go
main.go
...
type Article struct {
    Title string
    Author string
}
...

Next, we define a method called String on the Article type. The String method will return a string that represents the Article type:

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

main.go
main.go
...
func (a Article) String() string {
    return fmt.Sprintf("The %q article was written by %s.", a.Title, a.Author)
}
...

Then, in our main function, we create an instance of the Article type and assign it to the variable called a. We provide the values of "Understanding Interfaces in Go" for the Title field, and "Sammy Shark" for the Author field:

然后,在我们的main 函数中 ,我们创建Article类型的实例,并将其分配给名为a的变量 。 我们为"Understanding Interfaces in Go" Title字段提供"Understanding Interfaces in Go"的值,为“ Author字段提供"Sammy Shark"值:

main.go
main.go
...
a := Article{
    Title: "Understanding Interfaces in Go",
    Author: "Sammy Shark",
}
...

Then, we print out the result of the String method by calling fmt.Println and passing in the result of the a.String() method call:

然后,我们通过调用fmt.Println并传入a.String()方法调用的结果来打印出String方法的结果:

main.go
main.go
...
fmt.Println(a.String())

After running the program you’ll see the following output:

运行该程序后,您将看到以下输出:


   
     
     
     
     
Output
The "Understanding Interfaces in Go" article was written by Sammy Shark.

So far, we haven’t used an interface, but we did create a type that had a behavior. That behavior matched the fmt.Stringer interface. Next, let’s see how we can use that behavior to make our code more reusable.

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

定义接口 (Defining an Interface)

Now that we have our type defined with the desired behavior, we can look at how to use that behavior.

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

Before we do that, however, let’s look at what we would need to do if we wanted to call the String method from the Article type in a function:

但是,在执行此操作之前,让我们先看看如果要从函数的Article类型调用String方法,我们需要做些什么:

main.go
main.go
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())
}

In this code we add a new function called Print that takes an Article as an argument. Notice that the only thing the Print function does is call the String method. Because of this, we could instead define an interface to pass to the function:

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

main.go
main.go
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())
}

Here we created an interface called Stringer:

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

main.go
main.go
...
type Stringer interface {
    String() string
}
...

The Stringer interface has only one method, called String() that returns a string. A method is a special function that is scoped to a specific type in Go. Unlike a function, a method can only be called from the instance of the type it was defined on.

Stringer接口只有一种方法,称为String() ,该方法返回string 。 方法是一种特殊的功能,范围仅限于Go中的特定类型。 与函数不同,只能从定义类型的实例中调用方法。

We then update the signature of the Print method to take a Stringer, and not a concrete type of Article. Because the compiler knows that a Stringer interface defines the String method, it will only accept types that also have the String method.

然后,我们更新Print方法的签名以采用Stringer而不是Article的具体类型。 因为编译器知道Stringer接口定义了String方法,所以它将仅接受也具有String方法的类型。

Now we can use the Print method with anything that satisfies the Stringer interface. Let’s create another type to demonstrate this:

现在,我们可以将Print方法与满足Stringer接口的任何方法一起使用。 让我们创建另一种类型来演示这一点:

main.go
main.go
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: "Sammy Shark",
    }
    Print(a)

    b := Book{
        Title:  "All About Go",
        Author: "Jenny Dolphin",
        Pages:  25,
    }
    Print(b)
}

func Print(s Stringer) {
    fmt.Println(s.String())
}

We now add a second type called Book. It also has the String method defined. This means it also satisfies the Stringer interface. Because of this, we can also send it to our Print function:

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


   
     
     
     
     
Output
The "Understanding Interfaces in Go" article was written by Sammy Shark. The "All About Go" book was written by Jenny Dolphin. It has 25 pages.

So far, we have demonstrated how to use just a single interface. However, an interface can have more than one behavior defined. Next, we’ll see how we can make our interfaces more versatile by declaring more methods.

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

接口中的多种行为 (Multiple Behaviors in an Interface)

One of the core tenants of writing Go code is to write small, concise types and compose them up to larger, more complex types. The same is true when composing interfaces. To see how we build up an interface, we’ll first start by defining only one interface. We’ll define two shapes, a Circle and Square, and they will both define a method called Area. This method will return the geometric area of their respective shapes:

编写Go代码的核心租户之一是编写小的,简洁的类型,然后将它们组合成更大,更复杂的类型。 组成接口时也是如此。 为了了解如何构建接口,我们首先将仅定义一个接口。 我们将定义两个形状,即CircleSquare ,它们都将定义一个称为Area的方法。 此方法将返回其各自形状的几何区域:

main.go
main.go
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 smallest\n", l)
}

func Less(s1, s2 Sizer) Sizer {
    if s1.Area() < s2.Area() {
        return s1
    }
    return s2
}

Because each type declares the Area method, we can create an interface that defines that behavior. We create the following Sizer interface:

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

main.go
main.go
...
type Sizer interface {
    Area() float64
}
...

We then define a function called Less that takes two Sizer and returns the smallest one:

然后,我们定义一个名为Less的函数,该函数需要两个Sizer并返回最小的一个:

main.go
main.go
...
func Less(s1, s2 Sizer) Sizer {
    if s1.Area() < s2.Area() {
        return s1
    }
    return s2
}
...

Notice that we not only accept both arguments as the type Sizer, but we also return the result as a Sizer as well. This means that we no longer return a Square or a Circle, but the interface of Sizer.

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

Finally, we print out what had the smallest area:

最后,我们打印出面积最小的内容:


   
     
     
     
     
Output
{Width:5 Height:10} is the smallest

Next, let’s add another behavior to each type. This time we’ll add the String() method that returns a string. This will satisfy the fmt.Stringer interface:

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

main.go
main.go
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 smallest\n", 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 %.2f\n", s.String(), s.Area())
}

Because both the Circle and the Square type implement both the Area and String methods, we can now create another interface to describe that wider set of behavior. To do this, we’ll create an interface called Shaper. We’ll compose this of the Sizer interface and the fmt.Stringer interface:

因为CircleSquare类型都实现了AreaString方法,所以我们现在可以创建另一个接口来描述更广泛的行为。 为此,我们将创建一个名为Shaper的接口。 我们fmt.Stringer Sizer接口和fmt.Stringer接口组成:

main.go
main.go
...
type Shaper interface {
    Sizer
    fmt.Stringer
}
...

Note: It is considered idiomatic to try to name your interface by ending in er, such as fmt.Stringer, io.Writer, etc. This is why we named our interface Shaper, and not Shape.

注意:尝试以er结尾(例如fmt.Stringerio.Writer等)来命名您的接口是惯用的。这就是为什么我们将接口命名为Shaper而不是Shape

Now we can create a function called PrintArea that takes a Shaper as an argument. This means that we can call both methods on the passed in value for both the Area and String method:

现在,我们可以创建一个名为PrintArea的函数,该函数将Shaper作为参数。 这意味着我们可以在AreaString方法的传入值上调用这两个方法:

main.go
main.go
...
func PrintArea(s Shaper) {
    fmt.Printf("area of %s is %.2f\n", s.String(), s.Area())
}

If we run the program, we will receive the following output:

如果运行程序,将收到以下输出:


   
     
     
     
     
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

We have now seen how we can create smaller interfaces and build them up into larger ones as needed. While we could have started with the larger interface and passed it to all of our functions, it is considered best practice to send only the smallest interface to a function that is needed. This typically results in clearer code, as anything that accepts a specific smaller interface only intends to work with that defined behavior.

现在我们已经看到了如何创建较小的接口,并根据需要将其构建为较大的接口。 虽然我们可以从较大的接口开始并将其传递给我们的所有功能,但最好的做法是仅将最小的接口发送给所需的功能。 通常,这会导致代码更清晰,因为任何接受特定较小接口的内容都仅打算与该定义的行为一起使用。

For example, if we passed Shaper to the Less function, we may assume that it is going to call both the Area and String methods. However, since we only intend to call the Area method, it makes the Less function clear as we know that we can only call the Area method of any argument passed to it.

例如,如果我们将Shaper传递给Less函数,则可以假定它将同时调用AreaString方法。 但是,由于我们只打算调用Area方法,因此它使Less函数变得很清楚,因为我们知道我们只能调用传递给它的任何参数的Area方法。

结论 (Conclusion)

We have seen how creating smaller interfaces and building them up to larger ones allows us to share only what we need to a function or method. We also learned that we can compose our interfaces from other interfaces, including those defined from other packages, not just our packages.

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

If you’d like to learn more about the Go programming language, check out the entire How To Code in Go series.

如果您想了解有关Go编程语言的更多信息,请阅读完整的“ 如何在Go中编写代码”系列 。

翻译自: https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go

golang接口的使用场景

你可能感兴趣的:(python,java,golang,编程语言,go)