go语言:interface
一、go interface 是什么?
go语言中interface是一组方法集合,也是一种类型。我们可以把它看成一种定义内部方法的动态数据类型,任意实现了这些方法的数据类型都可以认为是特定的数据类型。
二、基本语法
- 定义接口
type Person interface {
// 声明方法
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
}
- 实现接口
func (t 自定义类型) method1(参数列表) 返回值列表 {
// 方法实现
}
func (t 自定义类型) method2(参数列表) 返回值列表 {
// 方法实现
}
- 说明
- interface里所有方法都没有方法体,interface的方法都是没有实现的方法。接口体现程序设计的多态和高内聚低耦合的思想。
- go语言的接口不需要显示的实现。
- go实现接口与方法有关,与接口本身没有名字没有特别大的关系。
- 变量需要实现接口所有的方法。
- 注意事项
- 1、接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。
package main
import "fmt"
type Person interface {
GetName() string
GetAge() uint32
}
type Student struct {
Name string
Age uint32
}
func (s Student) GetName() string {
return s.Name
}
func (s Student) GetAge() uint32 {
return s.Age
}
func main() {
student := Student{Name:"xiaoming", Age:12}
var person Person = student
fmt.Printf("name:%s, age:%d\n", person.GetName(), person.GetAge())
}
- 接口中所有的方法都没有方法体。
- 只要是自定义数据类型就可以实现接口,不仅仅是结构体类型。
- 一个自定义数据类型可以实现多个接口。
- 接口定义中不能含有变量。
- interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么就输出nil。
- 空接口interface{}没有任何方法,所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口类型。
三、go interface能做什么?
3.1、多态
go语言中是通过interface实现多态。
package main
import (
"fmt"
)
type TestInterface interface {
PrintClassName()
}
type TestA struct {
classname string
}
func (a TestA) PrintClassName() {
fmt.Println(a.classname)
}
type TestB struct {
classname string
}
func (b TestB) PrintClassName() {
fmt.Println(b.classname)
}
func main() {
var p TestInterface
testa := TestA{"classA"}
testb := TestB{"classB"}
p = testa
p.PrintClassName()
p = testb
p.PrintClassName()
}
3.2 依赖反转,让代码结构更整洁
我们来说一个比较常见的场景,一个 HTTP 接口,需要依赖数据库来获取用户得分并返回给调用方。比较直接的写法如下。
db := orm.NewDatabaseConnection() // 建立数据库链接
res := db.Query("select score from user where user == 'xxx'") // 通过 SQL 语句查询数据
return HTTP.Json(res) // 通过 Json 返回给调用方
这样写的坏处是,让 HTTP 的接口依赖了具体数据库底层的接口及实现,在数据库查询的功能没有开发完成时,HTTP 接口是不能开始开发的。同时对于如果后续存在更换数据库的可能,也不是很容易的扩展。比较推荐的写法是下面这样。
type UserDataStore interface {
GetUserScore(ctx context.Context, id string) (int, error)
DeleteUser(ctx context.Context, id string) error
}
// GetUserScoreHandler creates an HTTP handler that can get a user's score
func GetUserScoreHandler(userDataStore UserDataStore) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
id := req.Header.Get("x-user-id")
score, err := userDataStore.GetUserScore(req.Context(), id)
if err != nil {
fmt.Println("userDataStore.GetUserScore: ", err)
res.WriteHeader(500)
return
}
res.Write([]byte(fmt.Sprintf("%d", score)))
}
}
通过定义 Interface,将数据库与 HTTP 接口进行解耦,HTTP 接口不再依赖实际的数据库,代码可以单独的编写和编译,代码依赖和结构更加的清晰了。数据具体的实现逻辑只需按 Interface 实现对应的接口就可以了,最终实现了依赖的整体的反转。
3.3 提高程序的可测试性
回到刚才那个例子,如果我要对这个 HTTP 接口的逻辑做测试,我可以怎么做?如果你没有使用 Interface,那么测试肯定要依赖一个实际的 DB,我想你会去新建一个测试库,同时新建一些测试数据。
真的需要这样么?我们来一个比较好的实践。通过 Interface,可以很容易的实现一个 Mock 版本的类型,通过替换逻辑可以很方便的实现测试数据的构造。
type mockUserDataStore struct {
pendingError error
pendingScore int
deletedUsers []string
}
func (m *mockUserDataStore) GetUserScore(ctx context.Context, id string) (int, error) {
return m.pendingScore, m.pendingError
}
func (m *mockUserDataStore) DeleteUser(ctx context.Context, id string) error {
if m.pendingError != nil {
return m.pendingError
}
m.deletedUsers = append(m.deletedUsers, id)
return nil
}
以上就可以很方便的去控制接口调用的时候,获取用户得分和删除用户的逻辑。实际的测试也就变得简单了,也不用依赖真实的 DB,让测试更加的可靠了。
func TestGetUserScoreHandlerReturnsScore(t *testing.T) {
req := httptest.NewRequest("GET", "/idk", nil)
res := httptest.NewRecorder()
userDataStore := &mockUserDataStore{
pendingScore: 3, // mock 数据
}
handler := GetUserScoreHandler(userDataStore) // 将 Mock 的方法传递到实际调用的地方,实现动态的替换
handler(res, req)
resultStr := string(res.Body.Bytes())
expected := fmt.Sprintf("%d", userDataStore.pendingScore)
if res.Code != 200 {
t.Errorf("Expected HTTP response 200 but got %d", res.Code)
}
if resultStr != expected {
t.Errorf("Expected body to contain value %q but got %q", expected, resultStr)
}
}
资料:
http://legendtkl.com/2015/11/25/go-generic-programming/
http://legendtkl.com/2015/11/28/go-interface-reflect/
https://blog.csdn.net/yuqiang870/article/details/124746693
https://zhuanlan.zhihu.com/p/159658339
结构体初始化方式:https://www.cnblogs.com/liyutian/p/10050320.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通