golang接口的使用场景
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.
在本文中,我们将学习如何编写具有共同行为的自定义类型,这将使我们能够重用我们的代码。 我们还将学习如何为自己的自定义类型实现接口,这些接口将满足从另一个包定义的接口。
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”.
代码的第一行定义了一个称为Stringer
的type
。 然后声明它是一个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
行为的代码:
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
的新类型。 此类型具有Title
和Author
字段,并且均为字符串数据类型 :
...
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
类型上定义一个称为String
的method
。 String
方法将返回一个表示Article
类型的字符串:
...
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"
值:
...
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
方法的结果:
...
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
接口匹配。 接下来,让我们看看如何使用该行为使代码更可重用。
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
方法,我们需要做些什么:
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
方法。 因此,我们可以改为定义一个接口以传递给该函数:
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
的接口:
...
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
接口的任何方法一起使用。 让我们创建另一种类型来演示这一点:
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.
到目前为止,我们已经演示了如何仅使用单个接口。 但是,一个接口可以定义多个行为。 接下来,我们将看到如何通过声明更多方法来使我们的界面更加通用。
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代码的核心租户之一是编写小的,简洁的类型,然后将它们组合成更大,更复杂的类型。 组成接口时也是如此。 为了了解如何构建接口,我们首先将仅定义一个接口。 我们将定义两个形状,即Circle
和Square
,它们都将定义一个称为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 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
接口:
...
type Sizer interface {
Area() float64
}
...
We then define a function called Less
that takes two Sizer
and returns the smallest one:
然后,我们定义一个名为Less
的函数,该函数需要两个Sizer
并返回最小的一个:
...
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
返回。 这意味着我们不再返回Square
或Circle
,而是返回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
接口:
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:
因为Circle
和Square
类型都实现了Area
和String
方法,所以我们现在可以创建另一个接口来描述更广泛的行为。 为此,我们将创建一个名为Shaper
的接口。 我们fmt.Stringer
Sizer
接口和fmt.Stringer
接口组成:
...
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.Stringer
, io.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
作为参数。 这意味着我们可以在Area
和String
方法的传入值上调用这两个方法:
...
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
函数,则可以假定它将同时调用Area
和String
方法。 但是,由于我们只打算调用Area
方法,因此它使Less
函数变得很清楚,因为我们知道我们只能调用传递给它的任何参数的Area
方法。
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接口的使用场景