首页 > 其他分享 >go基础-接口

go基础-接口

时间:2023-08-03 15:33:19浏览次数:33  
标签:基础 Sales 接口 var 类型 interface go type

一、概述

接口是面向对象编程的重要概念,接口是对行为的抽象和概括,在主流面向对象语言Java、C++,接口和类之间有明确关系,称为“实现接口”。这种关系一般会以“类派生图”的方式进行,经常可以看到大型软件极为复杂的派生树,随着系统的功能不断增加,这棵“派生树”会变得越来越复杂。

Go语言接口模型非常特别,就目前观察是独创。go接口设计是非侵入式,只要类型方法是接口方法的超集,那么就认为类型实现了接口,两者之间不需要显示关联,当然也没有implements关键字,称为隐式实现。相比Java、C++主流面向对象语言需要显示实现接口,go的方式更加灵活、松散、耦合更低,当然也更加隐晦、代码可读性降低(当然有人不同意这种看法)。在不修改类型定义情况下,可以为其添加接口,这在Java、C++下是不可思议的。go的接口满足鸭子模型,所谓鸭子类型:只要走起路来像鸭子、叫起来也像鸭子,那么就可以把它当作鸭子。

 

二、基本使用

接口定义,描述一堆方法的集合,给出方法声明即可,不能有默认实现,也不能有变量

type User interface {
    Say()
    GetName() string
}

定了一个user接口,包含两个方法

 

任何类型都可以实现这两个方法,不需要显示使用implements关键字。满足两个条件,与接口方法签名完全一致,是接口方法的超集。即可判定类型实现了接口。

type Sales struct {                    // 定义类型
    name string
}

func (p *Sales) GetName() string {    // 接口方法一
    return p.name
}

func (p *Sales) Say() {                // 接口方法二
    fmt.Println("Hi, I'm", p.name)
}

func (p *Sales) peddle() {            
    fmt.Printf("%s peddle", p.name)
}

Sales类型满足两个条件,可判断实现了User接口。从代码上看两者没有直接关联,这就是隐式实现。

 

按照上面的两个条件,Sales也实现了如下接口

type Person interface {
    GetName() string
}

可以看到非常松散,就是这么简洁。再次强调只要满足两个条件:与接口方法签名一致,是接口方法的超集,即可判定类型实现了接口。从类型自身角度看,完全不知道自己实现了哪些接口。

 

通过实例调用方法

var sales Sales = Sales{name: "tom"}
sales.Say()

 

通过接口调用方法,只要类型实现了接口,就可以赋值给接口变量,并使用接口调用方法

var user User = &Sales{name: "tom"}        // 赋值给接口变量,注意是地址
user.Say()                                 // 通过接口调用方法

fmt.Printf("%T\n", user)                  // *main.Sales

接口是引用类型,和指针一样,只能指向实例的地址。

 

接口主要目标是解耦,通常称为面向接口编程,主流使用方式是函数形参是接口类型,调用时候传递接口变量,这也是接口存在的意义。

func PrintName(user User) {                    // 形参是User接口类型
    fmt.Println("姓名:", user.GetName())
}

var sales User = &Sales{name: "tom"}        
PrintName(sales)                            // 传入user接口变量

形参是接口类型,可传入所有实现了该接口的实例,不在依赖具体类型。

 

在标准库中大量使用接口。比如排序是普片需求,标准库提供了排序函数,形参是接口类型,任何实现了该接口的类型,都可直接使用排序函数

type Interface interface {        // 排序接口
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

func Sort(data Interface) {        // 标准库排序函数
    ...
}

 

和结构体一样,接口也支持继承特性

type User interface {
    Say()
    GetName() string
}

type Admin interface {
    User                // 继承User接口
    TeamName string        // 自有属性
}

需要实现包括继承的所有方法,才判定实现了该接口

 

并非只能使用结构体实现接口,其他自定义类型也可以实现接口,如下X类型实现了Plus接口

type Plus interface {
    incr()
}

type X int

func (x *X) incr() {
    *x += 1
    fmt.Println(*x)
}

 

三、接口断言

在接口变量上操作,用于检查接口类型变量是否实现了期望的接口或者具体的类型。使用接口的本质,就是实例类型和接口类型之间转换,而是否允许转换就依赖断言

value, ok := x.(T)

x 表示接口的类型,T 表示具体类型(也可以是接口),可根据该布尔值判断 x 是否为 T 类型。

  • 如果 T 是实例类型,类型断言会检查 x 的动态类型是否满足 T。如果成功返回 x 的动态值,其类型是 T。
  • 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果成功返回 值是 T 的接口值。
  • 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。

 

使用上面案例进行断言

var user User = &Sales{name: "tony"}

if value, ok := user.(User); ok {        // true,  接口断言
    value.Say()    
}

_, ok = user.(*Sales)                    // true, 具体类型断言, 注意这里使用了指针类型

 

注意,如果不接收第二个返回值(也就是 ok),断言失败时会 panic,对nil断言同样也会 panic。

admin := user.(Admin)    // Admin是管理员接口,断言失败panic

 

具体类型实例如何断言,可以先转为为接口,然后再进行断言

user1 := Sales{"tom"}

var data interface{} = user1      // 转换为空接口

if _, ok := data.(Sales); ok {    // 再进行断言
    fmt.Println("yes")
}

 

断言常见使用场景,异常捕获时判定错误类型

func ProtectRun(entry func()) {
    defer func() {
        err := recover()            // 获取错误类型
        
        switch err.(type) {            // 断言错误类型, 不同类型的错误采取不同的处理方式
        case runtime.Error: 
            fmt.Println("runtime error:", err)
        default:
            fmt.Println("error:", err)
        }
    }()

    ...
}

 

四、接口转换

go语言基本数据类型转换比较严格,所有基础类型不支持隐式转换,如下案例都不支持

s := "a" + 1    // err

// 不同长度的整型, 也支持自动转换
var i int = 10
var n int8 = 20
m := i+n      // err

 

go只能显示转换

s := "a" + string(1)      // a1

var i int = 10
var n int8 = 20
m := i + int(n)            // 30

 

使用接口的本质就是类型转换,赋值时转换为接口变量,执行时候转换为实例。 go 语言对于接口类型的转换则非常的灵活,对象和接口之间的转换、接口和接口之间的转换都可能是隐式的转换

var f *os.File
var a io.ReadCloser = f        // 隐式转换, *os.File 满足 io.ReadCloser 接口
var b io.Reader = a             // 隐式转换, io.ReadCloser 满足 io.Reader 接口
var c io.Closer = a             // 隐式转换, io.ReadCloser 满足 io.Closer 接口
var d io.Reader = a.(io.Reader) // 显式转换, io.Closer 不满足 io.Reader 接口

 

有时候对象和接口之间太灵活了,导致需要人为地限制这种无意之间的适配。常见的做法是定义一个含特殊方法来区分接口。比如 runtime 包中的 Error 接口就定义了一个特有的 RuntimeError 方法,用于避免其它类型无意中适配了该接口

type runtime.Error interface {
    error
    RuntimeError()
}

 

不过这种做法只是君子协定,如果有人刻意伪造接口也是很容易的。再严格一点的做法是给接口定义一个私有方法。只有满足了这个私有方法的对象才可能满足这个接口,而私有方法的名字是包含包的绝对路径名的,因此只能在包内部实现这个私有方法才能满足这个接口。测试包中的 testing.TB 接口就是采用类似的技术

type testing.TB interface {
    Error(args ...interface{})
    Errorf(format string, args ...interface{})
    ...
    private()  // 私有方法
}

 

不过这种通过私有方法禁止外部对象实现接口的做法也是有代价的,首先是这个接口只能包内部使用,外部包正常情况下是无法直接创建满足该接口对象的;其次,这种防护措施也不是绝对的,恶意的用户依然可以绕过这种保护机制。通过嵌入匿名的 testing.TB 接口来伪造私有的 private 方法,因为接口方法是延迟绑定,编译时 private 方法是否真的存在并不重要。

type TB struct {
    testing.TB
}

func (p *TB) Fatal(args ...interface{}) {
    fmt.Println("TB.Fatal disabled!")
}

func main() {
    var tb testing.TB = new(TB)
    tb.Fatal("Hello, playground")
}

在自己的 TB 结构体类型中重新实现了 Fatal 方法,然后通过将对象隐式转换为 testing.TB 接口类型(因为内嵌了匿名的 testing.TB 对象,因此是满足 testing.TB 接口的),然后通过 testing.TB 接口来调用我们自己的 Fatal 方法。

 

五、空接口

接口定义没有声明任何方法,称为空接口,按照go规范任何类型都实现了空接口,因为都满足了两个实现条件。这就比较有意思了,空接口可以等于任何值,类似Java中的Object对象。

var data interface{}        // 定义空接口变量

data = 1
fmt.Printf("type=%T, value=%v\n", data, data)
data = "hello"
fmt.Printf("type=%T, value=%v\n", data, data

输出如下

type=int, value=1
type=string, value=hello

 

函数形参是空接口类型,就表示可接收任何类型,然后再在断言,不同的类型,采用不同的逻辑,在开发框架层时经常使用

func assertion(T interface{}) {
    switch T.(type) {
    case User:
        fmt.Println("user")
    case Admin:
        fmt.Println("admin")
    default:
        fmt.Println("default")
    }
}

T.(type)语法只能在switch中使用,可以理解定制语法糖,否则需要使用if逐个类型断言

 

空接口在标准库空也有普遍使用,比如panic函数终止程序时,可传递空接口类型的参数,捕获错误时可获取

type any = interface{}

func panic(v any)

 

标签:基础,Sales,接口,var,类型,interface,go,type
From: https://www.cnblogs.com/asdfzxv/p/17603442.html

相关文章

  • 外键字段的增删改查,多表查询,正反向的概念,子查询,多表查询之连表查询(基于双下划线
    外键字段的增删改查#多对多的外键增删改查图书和作者是多对多,借助于第三张表实现的,如果想绑定图书和作者的关系,本质上就是在操作第三方表#如何操作第三张表问题:让你给图书添加一个作者,他俩的关系可是多对多#多对多的增删该查#让你给图书id=2添加一个作者id=1b......
  • 遇到:ValueError: not enough values to unpack (expected 2, got 1) 错误应该如何解决
    遇到"ValueError:notenoughvaluestounpack(expected2,got1)"错误时,通常是因为你在尝试解包(unpack)一个包含不足两个值的可迭代对象。要解决这个问题,你可以考虑以下几个步骤:检查可迭代对象的长度:确保你的可迭代对象包含至少两个值。如果你的可迭代对象只有一个值,那么解包......
  • 多语言API接口接入电商平台获得商品快递费用源代码演示示例
     商品快递费用API接口的作用是通过调用接口获取特定商品的快递费用信息。具体而言,该接口可以提供以下功能和作用:实时获取快递费用:通过API接口可以实时查询不同快递公司对于指定商品的运费费用。用户可以根据商品的重量、尺寸、寄送地址等信息,调用接口获取最准确的快递费用。便于物......
  • 云计算笔记(一):基础概念
    本文用于收集和整理云计算设计的概念。现在的云计算有些过热(“人人都在谈论它,但没有人真正知道它”),很多研究都挂上了这个名词来显示其时髦。从某种意义上讲:云计算isnothingnew,只是概念的创造。重新整理了网络资源,特别适合与运营商(包括亚马逊)来整理他们的产品和服务。云计算提供......
  • Go + Mongo 实现有数据不操作,没数据添加
    data:=interface{}//要添加的内容可以是结构体,也可以是bsonfilter:=bson.M{}//过滤内容,是否插入信息以这个为准,如果有这个内容,则不进行任何操作//附加参数upsert:=trueupsertOptions:=options.UpdateOptions{UpdateOptions:&options.UpdateOptions......
  • Go 语言入门指南: 环境搭建、基础语法和常用特性解析 | 青训营
    Go语言入门指南:环境搭建、基础语法和常用特性解析|青训营从零开始Go语言简介Go是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。Go是从2007年末由RobertGriesemer,RobPike,KenThompson主持开发,后来还加入了IanLanceTaylor,RussCox等人,并最终......
  • HtmlAgilityPack 网页数据抓取基础应用
    1vardoc=newHtmlAgilityPack.HtmlDocument();2stringhtml="";3doc.LoadHtml(html);4Func<HtmlAgilityPack.HtmlNodeCollection,string,Dictionary<string,string>,bool,List<Htm......
  • 基础算法
    复健\(Day3\)一些基础的算法(模板)\(1.\)位运算进行状压\(DP\)时常用到位运算\(64\)位整数乘法https://www.acwing.com/problem/content/92/#include<iostream>#include<cstdio>#defineLLlonglongusingnamespacestd;intmain(){LLa,b,p;cin>>a>&g......
  • 递进-Validator接口
    Validator接口是Spring框架中用于数据验证的接口,它可以用于在数据绑定之前对用户输入的数据进行验证和校验。通过实现Validator接口,开发者可以自定义验证逻辑,对数据进行合法性和安全性校验,以确保应用程序处理的数据是有效和正确的。以下是一个简单的例子,展示如何使用Valida......
  • 《Kali渗透基础》12. 无线渗透(二)
    @目录1:无线协议栈1.1:ifconfig1.2:iwconfig1.3:iw1.4:iwlist2:无线网卡配置2.1:查看无线网卡2.2:查看信道频率2.3:扫描附近AP2.4:侦听接口添加与删除3:RADIOTAP头部4:MPDU4.1:基本知识4.2:MPDU介绍4.3:Header4.3.1:FrameControl4.3.1.1:Type/SubType4.3.1.2:ToDS/FromDS4.3.2:Duration/I......