go 面向接口编程知识点
接口定义与格式
隐式实现及实现条件
接口赋值
空接口
接口嵌套
类型断言
多态
接口定义与格式
接口 (interface) 是一种类型, 用来定义行为(方法). 这句话有两个重点, 类型和定义行为.
首先解释定义行为:
接口即一组方法定义的集合, 定义了对象的一组行为, 就是定义了一些函数, 由具体的类型实例实现具体的方法.
换句话说, 一个接口就是定义(规范或约束), 接口并不会实现这些方法, 具体的实现由类实现, 实现接口的类必须严格按照接口的声明来实现接口提供的所有功能. 接口的作用应该是将定义与实现分离, 降低耦合度.
在多人合作开发同一个项目时,接口表示调用者和设计者的一种约定, 事先定义好相互调用的接口可以大大提高开发的效率. 有了接口, 就可以在不影响现有接口声明的情况下, 修改接口的内部实现, 从而使兼容性问题最小化.
接口的定义格式:
- type Namer interface {
- Method1(param_list) return_type // 方法名(参数列表) 返回值列表
- Method2(param_list) return_type // 方法名(参数列表) 返回值列表
- ......
- }
隐式实现及实现条件
怎么实现接口:
实现接口的类并不需要显式声明, 只需要实现接口所有的函数就表示实现了该接口, 而且类还可以拥有自己的方法.
接口能被哪些类型实现:
接口可以被结构体实现, 也可以被函数类型实现.
接口被实现的条件:
接口被实现的条件一: 接口的方法与实现接口的类型方法格式一致(方法名, 参数类型, 返回值类型一致).
接口被实现的条件二: 接口中所有方法均被实现.
- package main
- import "fmt"
- type Shaper interface {
- Area() float64
- // Perimeter() float64
- }
- type Rectangle struct {
- length float64
- width float64
- }
- // 实现 Shaper 接口中的方法
- func (r *Rectangle) Area() float64 {
- return r.length * r.width
- }
- // Set 是属于 Rectangle 自己的方法
- func (r *Rectangle) Set(l float64, w float64) {
- r.length = l
- r.width = w
- }
- func main() {
- rect := new(Rectangle) // 创建指针类型的结构体实例(类实例)
- rect.Set(2, 3)
- areaIntf := Shaper(rect) // 这里将指针类型实例赋值给接口, 下面会介绍.
- fmt.Printf("The rect has area: %f\n", areaIntf.Area())
- }
接口赋值
现在来解释接口是一个类型, 本质是一个指针类型, 那么什么样的值可以赋值给接口, 有两种: 实现了该接口的类或者接口.
1. 将对象赋值给接口
当接口实例中保存了自定义类型的实例后, 就可以直接从接口上调用它所保存的实例的方法.
- package main
- import (
- "fmt"
- )
- // 定义接口
- type Testinterface interface{
- Teststring() string
- Testint() int
- }
- // 定义结构体
- type TestMethod struct{
- name string
- age int
- }
- // 结构体的两个方法隐式实现接口
- func (t *TestMethod)Teststring() string{
- return t.name
- }
- func (t *TestMethod)Testint() int{
- return t.age
- }
- func main(){
- T1 := &TestMethod{"ling",34}
- T2 := TestMethod{"gos",43}
- // 接口本质是一种类型
- // 接口赋值: 只要类实现了该接口的所有方法, 即可将该类赋值给这个接口
- var Test1 Testinterface // 接口只能是值类型
- Test1 = T1 //TestMethod 类的指针类型实例传给接口
- fmt.Println(Test1.Teststring())
- fmt.Println(Test1.Testint())
- Test2 := T2 //TestMethod 类的值类型实例传给接口
- fmt.Println(Test2.Teststring())
- fmt.Println(Test2.Testint())
- }
2. 将接口赋值给另一个接口
1. 只要两个接口拥有相同的方法列表(与次序无关), 即是两个相同的接口, 可以相互赋值
2. 接口赋值只需要接口 A 的方法列表是接口 B 的子集(即假设接口 A 中定义的所有方法, 都在接口 B 中有定义), 那么 B 接口的实例可以赋值给 A 的对象. 反之不成立, 即子接口 B 包含了父接口 A, 因此可以将子接口的实例赋值给父接口.
3. 即子接口实例实现了子接口的所有方法, 而父接口的方法列表是子接口的子集, 则子接口实例自然实现了父接口的所有方法, 因此可以将子接口实例赋值给父接口.
3. 接口类型作为参数
第一点已经说了可以将实现接口的类赋值给接口, 而将接口类型作为参数很常见. 这时, 那些实现接口的实例都能作为接口类型参数传递给函数 / 方法.
- package main
- import (
- "fmt"
- )
- //Shaper 接口
- type Shaper interface {
- Area() float64
- }
- // Circle struct 结构体
- type Circle struct {
- radius float64
- }
- // Circle 类型实现 Shaper 中的方法 Area()
- func (c *Circle) Area() float64 {
- return 3.14 * c.radius * c.radius
- }
- func main() {
- // Circle 的指针类型实例
- c1 := new(Circle)
- c1.radius = 2.5
- // 将 Circle 的指针类型实例 c1 传给函数 myArea, 接收类型为 Shaper 接口
- myArea(c1)
- }
- func myArea(n Shaper) {
- fmt.Println(n.Area())
- }
空接口
空接口是指没有定义任何接口方法的接口. 没有定义任何接口方法, 意味着 Go 中的任意对象都已经实现空接口(因为没方法需要实现), 只要实现接口的对象都可以被接口保存, 所以任意对象都可以保存到空接口实例变量中.
空接口的定义方式:
type empty_int interface {}
更常见的, 会直接使用 interface{}作为一种类型, 表示空接口. 例如:
- // 声明一个空接口实例
- var i interface{
- }
再比如函数使用空接口类型参数:
func myfunc(i interface{})
如何使用空接口
可以定义一个空接口类型的 array,slice,map,struct 等, 这样它们就可以用来存放任意类型的对象, 因为任意类型都实现了空接口.
- package main
- import "fmt"
- func main() {
- any := make([]interface{}, 5)
- any[0] = 11
- any[1] = "hello world"
- any[2] = []int{11, 22, 33, 44}
- for _, value := range any {
- fmt.Println(value)
- }
- }
- 11
- hello world
- [11 22 33 44]
- <nil>
- <nil>
通过空接口类型, Go 也能像其它动态语言一样, 在数据结构中存储任意类型的数据.
接口嵌套
接口可以嵌套, 嵌套的内部接口将属于外部接口, 内部接口的方法也将属于外部接口.
另外在类型嵌套时, 如果内部类型实现了接口, 那么外部类型也会自动实现接口, 因为内部属性是属于外部属性的.
- type ReadWrite interface {
- Read(b Buffer) bool
- Write(b Buffer) bool
- }
- type Lock interface {
- Lock()
- Unlock()
- }
- type File interface {
- //ReadWrite 为内部接口
- ReadWrite
- //Lock 为内部接口
- Lock
- Close()
- }
类型断言
类型断言为判断一个类型有没有实现接口.
假如我现在写了一个结构体类型 MyFile 来实现上面的 File 接口, 那么我如何知道 MyFile 是否实现了 File 接口呢?
- package main
- import "fmt"
- type MyFile struct{}
- func (m *MyFile) Read() bool {
- fmt.Printf("Read()\n")
- return true
- }
- // ...
- // 假设我这里相继实现了 Write(), Lock(),Unlock() 和 Close() 方法
- func main() {
- my := new(MyFile)
- fIntf := File(my)
- // 看这里, 看这里
- if v, ok := fIntf.(*MyFile); ok {
- v.Read()
- }
- }
类型断言的格式:
- if v, ok : = varI.(T) ; ok {
- // checked type assertion
- //do something
- return
- }
如果 v 是 varI 转换到类型 T 的值, ok 会是 true; 否则 v 是类型 T 的零值, ok 是 false.
要是多个类型实现了同一个接口, 比如前面的 areaIntf, 要如何测试呢?
那就要用 type-switch 来判断了.
- switch t := areaIntf.(type) {
- case *Rectangle:
- // do something
- case *Triangle:
- // do something
- default:
- // do something
- }
多态
1, 多个类型 (结构体) 可以实现同一个接口.
2, 一个类型 (结构体) 可以实现多个接口.
3, 实现接口的类 (结构体) 可以赋值给接口.
- package main
- import "fmt"
- type Shaper interface {
- Area() float64
- }
- // ==== Rectangle ====
- type Rectangle struct {
- length float64
- width float64
- }
- // 实现 Shaper 接口中的方法
- func (r *Rectangle) Area() float64 {
- return r.length * r.width
- }
- // Set 是属于 Rectangle 自己的方法
- func (r *Rectangle) Set(l float64, w float64) {
- r.length = l
- r.width = w
- }
- // ==== Triangle ====
- type Triangle struct {
- bottom float64
- hight float64
- }
- func (t *Triangle) Area() float64 {
- return t.bottom * t.hight / 2
- }
- func (t *Triangle) Set(b float64, h float64) {
- t.bottom = b
- t.hight = h
- }
- // ==== Triangle End ====
- func main() {
- rect := new(Rectangle)
- rect.Set(2, 3)
- areaIntf := Shaper(rect) // 这种方法只能将指针类型的类示例赋值给接口
- fmt.Printf("The rect has area: %f\n", areaIntf.Area())
- triangle := new(Triangle)
- triangle.Set(2, 3)
- areaIntf = Shaper(triangle) // 这种方法只能将指针类型的类示例赋值给接口
- fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
- }
来源: https://www.cnblogs.com/-wenli/p/12316479.html