接口设计示例 二

这个示例与前文中的设计原则里的张三李四开车非常相似。只不过这里是咱自己总结的。。。不足的地方后续慢慢再改~

在下面的例子中,我们把 main() 中的代码当作外部代码,通过调用我们暴露出来的接口(这里的接口仅仅是一个函数),来获取想要的数据。其实就等于说,除了 main() 以外,其他的代码都当作是其他 package 内的代码。然后通过 main() 调用。在网上的很多代码示例,都是这么来搞得。

只有一个 struct

现在我想要计算一个图形周长和面积。假定现在只有一个正方形,那么我定义一个正方形

// Square 正方形的属性
type Square struct {
	side float32
}

并且有两个方法来计算正方形的面积与周长

// Area 正方形求面积的方法,接收了正方形的结构体并使用结构体中的边长属性来计算面积
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}
// Perimeter 正方形求周长的方法
func (sq *Square) Perimeter() float32 {
	return sq.side * 4
}

并且提供一个函数,供其他人调用以便获取图形的面积与周长,只需要调用 PrintResult() 函数,并传递参数即可。

// PrintResult 输出计算结果
func PrintResult(shape string, s*Square) {
	fmt.Printf("%s的面积:%.2f\n", shape, s.Area())
	fmt.Printf("%s的周长:%.2f\n", shape, s.Perimeter())
}

下面这是一段完整的代码,我们可以在 main() 中调用 PrintResult() 来获取正方形的周长和面积。

package main
import "fmt"
// Square 正方形的属性
type Square struct {
	side float32
}
// Area 正方形求面积的方法,接收了正方形的结构体并使用结构体中的边长属性来计算面积
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}
// Perimeter 正方形求周长的方法
func (sq *Square) Perimeter() float32 {
	return sq.side * 4
}
// PrintResult 输出计算结果
func PrintResult(shape string, s*Square) {
	fmt.Printf("%s的面积:%.2f\n", shape, s.Area())
	fmt.Printf("%s的周长:%.2f\n", shape, s.Perimeter())
}
// Formula 计算不同图形的周长和面积
func main() {
	square := &Square{side: 5}
	PrintResult("正方形", square)
}

增加一个 struct

现在我还想计算另一个图形的面积和周长,那么我就需要增加一个结构体和对应的方法:

// Rectangle 长方形的属性
type Rectangle struct {
	length float32
	width  float32
}

// Area 矩形求面积的方法
func (r Rectangle) Area() float32 {
	return r.length * r.width
}

// Perimeter 矩形求周长的方法
func (r Rectangle) Perimeter() float32 {
	return r.length*2 + r.width*2
}

并且,此时我需要修改我本身的代码,因为PrintResult() 是用来接收正方形参数的,所以我需要添加一个函数,来接收长方形的参数。

假如我现在再添加一个用于计算长方形的函数

// PrintResult2 输出计算结果
func PrintResult2(shape string, result *Rectangle) {
	fmt.Printf("%s的面积:%.2f\n", shape, result.Area())
	fmt.Printf("%s的周长:%.2f\n", shape, result.Perimeter())
}

下面这是一段完整的代码,我们可以在 main() 中调用 PrintResult()PrintResult2() 来获取正方形与长方形的周长和面积。

package main

import "fmt"

// Square 正方形的属性
type Square struct {
	side float32
}

// Rectangle 长方形的属性
type Rectangle struct {
	length float32
	width  float32
}

// Area 正方形求面积的方法,接收了正方形的结构体并使用结构体中的边长属性来计算面积
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

// Perimeter 正方形求周长的方法
func (sq *Square) Perimeter() float32 {
	return sq.side * 4
}

// Area 矩形求面积的方法
func (r Rectangle) Area() float32 {
	return r.length * r.width
}

// Perimeter 矩形求周长的方法
func (r Rectangle) Perimeter() float32 {
	return r.length*2 + r.width*2
}

// PrintResult 输出计算结果
func PrintResult(shape string, result *Square) {
	fmt.Printf("%s的面积:%.2f\n", shape, result.Area())
	fmt.Printf("%s的周长:%.2f\n", shape, result.Perimeter())
}

// PrintResult2 输出计算结果
func PrintResult2(shape string, result *Rectangle) {
	fmt.Printf("%s的面积:%.2f\n", shape, result.Area())
	fmt.Printf("%s的周长:%.2f\n", shape, result.Perimeter())
}

// Formula 计算不同图形的周长和面积
func main() {
	square := &Square{side: 5}
	rectangle := &Rectangle{length: 4, width: 3}
	PrintResult("正方形", square)
	PrintResult2("矩形", rectangle)

}

这个时候,我们可以看到,我们为了提供更多的功能,而修改了原始代码。如果是一个很大的项目,不同的结构体在不同的 package 内,每增加一个新的需求,我们就需要修改暴露的接口(也就是修改 PrintResult()),而对方在使用时,也需要使用新的函数进行调用。那么有没有一种办法,可以只对外暴露一个接口,就可以随时扩展接口内的能力呢。

这个需求,就是 Go 中 Interface(接口) 可以实现的。从这段描述中也可以看到,**implements(实现了) **是一个很关键的描述,如果想要通过一个统一的接口还获取想要的结果,那么被获取者(也就是 struct) 就要实现这个接口。而想要实现,那么 struct 就必须要实现 Interface 定义的所有方法。

统一接口

在上面的代码中,各种图形都实现了两个方法(即.计算图形面积、计算图形周长),所以我们就声明一个包含这两个方法的接口

// FormulaOperator 定义了一个接口,包含两个方法
// 该接口实现了一个求面积的方法和一个求周长的方法
type FormulaOperator interface {
	Area() float32
	Perimeter() float32
}

然后暴露的接口改为如下代码即可

// PrintResult 输出计算结果
func PrintResult(shape string, result FormulaOperator) {
	fmt.Printf("%s的面积:%f\n", shape, result.Area())
	fmt.Printf("%s的周长:%f\n", shape, result.Perimeter())
}

然后在 main() 中这样调用接口:

// Formula 计算不同图形的周长和面积
func main() {
	square := &Square{side: 5}
	rectangle := Rectangle{length: 4, width: 3}
	PrintResult("正方形", square)
	PrintResult("矩形", rectangle)
}

只需要在调用一个函数,传递参数,指定要计算的图形,即可获取改图形的周长和面积了,具体代码如下:

package main

import "fmt"

// FormulaOperator 定义了一个接口,包含两个方法
// 该接口实现了一个求面积的方法和一个求周长的方法
type FormulaOperator interface {
	Area() float32
	Perimeter() float32
}

// 定义了两个结构体,结构体实现了接口FormulaOperator
// 名为正方形的结构体,里面有一个参数是边长。名为矩形的结构体里面有两个参数

// Square 正方形的属性
type Square struct {
	side float32
}

// Rectangle 长方形的属性
type Rectangle struct {
	length float32
	width  float32
}

// Area 正方形求面积的方法,接收了正方形的结构体并使用结构体中的边长属性来计算面积
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

// Perimeter 正方形求周长的方法
func (sq *Square) Perimeter() float32 {
	return sq.side * 4
}

// Area 矩形求面积的方法
func (r Rectangle) Area() float32 {
	return r.length * r.width
}

// Perimeter 矩形求周长的方法
func (r Rectangle) Perimeter() float32 {
	return r.length*2 + r.width*2
}

// PrintResult 输出计算结果
func PrintResult(shape string, result FormulaOperator) {
	fmt.Printf("%s的面积:%f\n", shape, result.Area())
	fmt.Printf("%s的周长:%f\n", shape, result.Perimeter())
}

// Formula 计算不同图形的周长和面积
func main() {
	square := &Square{side: 5}
	rectangle := Rectangle{length: 4, width: 3}
	PrintResult("正方形", square)
	PrintResult("矩形", rectangle)
}

最后修改 July 4, 2023: update (0cc1c818)