译者:baiyutang
原文:https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-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: "Sammy Shark",
}
fmt.Println(a.String())
}
复制代码
我们要做的第一件事是创建一个名为 Article
的新类型,这个类型具有 Title
和 Author
字段并且他们都是 字符类型。
...
type Article struct {
Title string
Author string
}
...
复制代码
然后,在我们的 main
函数,我们创建一个 Article 的接口类型并赋值给一个名为 a
的变量。我们给 Title
字段提供值为 "Understanding Interfaces in Go"
,给 Author 提供值为 "Sammy Shark"
:
...
a := Article{
Title: "Understanding Interfaces in Go",
Author: "Sammy Shark",
}
...
复制代码
然后,我们通过调用 fmt.Println
打印 String
方法的结果。并且把 a.String
方法调用的结果传过去。
...
fmt.Println(a.String())
复制代码
在运行程序之后,我们将看到下面的输出:
The "Understanding Interfaces in Go" article was written by Sammy Shark.
复制代码
到目前为止,我们没有使用接口,但是我们刚创建具有一个行为的类型。行为匹配到 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",
}
Print(a)
}
func Print(a Article) {
fmt.Println(a.String())
}
复制代码
接口中的多个行为
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
}
复制代码
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())
}
复制代码
总结
我们已经看到如何创建更小的接口,并将其构建为更大的接口,从而只共享我们需要的功能或方法。我们也学到了我们可以从其他接口组合我们接口,包括这些从其他包定义的接口,不仅仅是我们的。
评论