前言

在之前的文章中我们说过,golang 是通过 结构体(struct)-方法(method)-接口(interface) 的组合使用来实现面向对象的思想。在前文 Golang 复合类型 和 Golang method 方法详解 已经详细介绍过 structmethod,本文将介绍 golang 面向对象的另一个重要组成部分:接口(interface)

文章目录

  • 前言
  • 接口
    • 接口概念
    • 接口定义
    • 封装性
    • 接口查询
    • 接口赋值
      • 将对象实例赋值给接口
      • 将一个接口赋值给另一个接口
    • 接口组合
  • Any 类型
  • sort interface 示例

接口

接口概念

接口是一种抽象的类型,描述了一系列方法的集合,作用是对一系列具有联系的方法做出抽象和概括。接口只定义方法名和参数,而不包含具体的实现,这种抽象的方式可以让程序变得更加灵活更加通用。

在很多语言中,接口都是侵入式的,侵入式接口的意思是实现类需要明确声明自己实现了某个接口,这就带来了一个很矛盾的问题,比如 A 调用了 B 的接口,那么 A 一定会希望接口被设计成自己想要使用的样子,但是 B 才是接口的实现方,基于模块设计的单向依赖原则,B 在实现自身的业务时,不应该关心某个具体使用方的要求,一个接口被定义的时候,并不知道自己的方法会被谁实现,也不知道会被怎么样实现。因此,侵入式接口一直是面向对象编程中一个经常遭受质疑的特性。

不同的是,golang 的接口是一种 非侵入式 的接口,一个类型不需要明确声明,只要实现了接口的所有方法,这个类型就实现了该接口,这个类型的对象就是这个接口类型的实例。 因此,在 golang 中,不再需要定义类的继承关系,而且在定义接口时候,只需要关心自己需要提供哪些方法,其他的方法有使用方按需定义即可。

接口定义

/* 定义接口 */
type interface_name interface {method_name1(input_paras...) [return_type]method_name2(input_paras...) [return_type]method_name3(input_paras...) [return_type]
}/* 定义结构体 */
type struct_name struct {/* variables */
}/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1(input_paras...) [return_type] {/* 方法实现 */
}func (struct_name_variable struct_name) method_name2(input_paras...) [return_type] {/* 方法实现*/
}func (struct_name_variable struct_name) method_name3(input_paras...) [return_type] {/* 方法实现*/
}

go语言的源码中大量使用到了接口,比如说在前面的文章中多次使用到的 error 类型

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {Error() string
}

封装性

接口是 golang 封装性的重要一环,接口可以封装具体类型和类型的值,即使一个类型还有别的方法,接口的实例也只能调用接口暴露出来的方法。如下:

type HelloInterface interface {Hello()
}type User struct {}func (f *User) Hello() {fmt.Println("hello")
}func (f *User) Bye() {fmt.Println("bye")
}func InterfaceTest() {u := &User{}u.Hello()   // oku.Bye()        // okvar user HelloInterface = new(User) // 接口实例化user.Hello() // okuser.Bye() // Compile error: user.Bye undefined (type HelloInterface has no field or method Bye)
}

注意,使用一个接口对象必须要先实例化,否则接口对象的值为 nil,调用 nil 对象的任何方法都会产生空指针 panic。

接口查询

和查询某个元素是否在 map 中类似,Golang 也内置了接口查询,可以使用和 map 类似的语法来检查对象实例是否实现了接口,如下:

var user HelloInterface = new(User) // 接口实例化
if u1, ok := user.(HelloInterface); ok {fmt.Println(u1)   // yes
}if u2, ok := user.(Reader); ok {fmt.Println(u2)   // no
}

也可以查询对象是否是某个类型

if u3, ok := user.(*User); ok {fmt.Println(u3)
}

Golang 还可以使用断言和反射来进行类型查询,这两个内容会在后续的文章中介绍。

接口赋值

将对象实例赋值给接口

要将对象实例赋值给接口,要求该对象实例实现了接口要求的所有方法。如:

type Integer intfunc (a Integer) Less(b Integer) bool {return a < b
} func (a *Integer) Add(b Integer) {*a += b
}type LessAdder interface {Less(b Integer) boolAdd(b Integer)
}var a Integer = 1
var b LessAdder = &a

注意,此处赋值时用 &a 而不是 a, 因为 Go 会自动为 *Integer 生成一个新的 Less 方法

func (a *Integer) Less(b Integer) bool {         return (*a).Less(b)
}

从而让 *Integer 既存在 Less(),又存在 Add(), 满足接口 LessAdder

将一个接口赋值给另一个接口

在Go语言中,只要两个接口拥有相同的方法列表(不用考虑顺序),那么它们就是等同的,可以相互赋值。

package onetype ReadWriter1 interface {Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error)
}// 第二个接口位于另一个包中:
package twotype ReadWriter2 interface {Write(buf []byte) (n int, err error) Read(buf []byte) (n int, err error)
}// 可以相互赋值
var file1 two.ReadWriter2 = new(File)
var file2 one.ReadWriter1 = file1
var file3 two.ReadWriter2 = file2

接口赋值并不要求两个接口必须等价。如果接口 A 的方法列表是接口 B 的方法列表的子集, 那么接口 B可以赋值给接口 A,但是 A 不可以赋值给 B。(大接口可以赋值给小接口)

接口组合

类似于结构内嵌,接口的组合也是使用匿名机制实现的,如下:

type Reader interface {Read(p []byte) (n int, err error)
}type Writer interface {Write(p []byte) (n int, err error)
}// 将 Read 和 Write 方法组合
// ReadWriter 接口既能做 Reader 接口的所有事情,又能做 Writer 接口的所有事情。type ReadWriter interface {ReaderWriter
}// 与下面的写法完全等价
type ReadWriter interface {Read(p []byte) (n int, err error) Write(p []byte) (n int, err error)
}

Any 类型

Go语言中任何对象实例都满足空接口 interface{},所以可以把 interface{} 看作可以指向任何对象的 Any 类型,当函数可以接受任意的对象实例时,我们会将其声明为 interface{},从而可以接受任意类型的对象,然后再使用类型断言来对该参数进行转换,再做后续的处理(具体内容参看类型断言的博客)。

最典型的例子是标准库 fmt 中 PrintXXX 系列的函数,例如:

func Printf(fmt string, args ...interface{})
func Println(args ...interface{})

sort interface 示例

接下来,让我们通过介绍内置的 sort 包来加深一下对接口的理解,顺便了解一下这个常用包的使用。

Golang 的 sort 包中通过接口的方式内置了可以对任何类型的列表进行快排的功能,下面我们一起来看看它是如何使用的。

首先我们要先了解 sort.Interface 源码中定义了哪些方法:

// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {// Len is the number of elements in the collection.Len() int// Less reports whether the element with// index i should sort before the element with index j.Less(i, j int) bool// Swap swaps the elements with indexes i and j.Swap(i, j int)
}

可以看到,我们需要先定义三个方法:

  1. 计算列表长度的方法
  2. 比较两个元素的方法
  3. 交换两个元素的方法

因此,我们需要定义一种类型,这种类型要同时具有以上三种方法,比如一个简单的 Student 类

type Student struct {ID int64Name string
}type StudentSlice []*Studentfunc (s StudentSlice) Len() int {return len(s)
}func (s StudentSlice) Less(i, j int) bool {return s[i].ID < s[j].ID
}func (s StudentSlice) Swap(i, j int) {s[i], s[j] = s[j], s[i]
}

接下来,对一个 Student 进行初始化

 s1 := &Student{ID:   1,Name: "A",}s2 := &Student{ID:   2,Name: "B",}s3 := &Student{ID:   3,Name: "C",}students := []*Student{s3, s1, s2}

准备工作已经做好,接下来我们先来看一下 sort.Sort 函数的源码

// Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {n := data.Len()quickSort(data, 0, n, maxDepth(n))
}

可以看到,函数的入参是一个 sort.Interface 类型的对象,然后对这个对象进行快排操作,所以,要使用这个函数,我们还需要把 []*Student 类型转换成 StudentSlice,由于 StudentSlice 实现了 sort.Interface 的所有方法,所以 StudentSlice 的对象就是 sort.Interface 类型的对象。

sort.Sort(StudentSlice(students))

完成后,打印 students,我们就可以看到排好序的列表了。

{ID:1 Name:A}
{ID:2 Name:B}
{ID:3 Name:C}

对于自定义的类型,要进行排序就要完成上述的所有操作,幸运的是,对于常用基本类型,go 源码已经为我们准备好了一系列可以直接调用的方法。

// Ints sorts a slice of ints in increasing order.
func Ints(a []int) { Sort(IntSlice(a)) }// Float64s sorts a slice of float64s in increasing order
// (not-a-number values are treated as less than other values).
func Float64s(a []float64) { Sort(Float64Slice(a)) }// Strings sorts a slice of strings in increasing order.
func Strings(a []string) { Sort(StringSlice(a)) }// IntsAreSorted tests whether a slice of ints is sorted in increasing order.
func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) }// Float64sAreSorted tests whether a slice of float64s is sorted in increasing order
// (not-a-number values are treated as less than other values).
func Float64sAreSorted(a []float64) bool { return IsSorted(Float64Slice(a)) }// StringsAreSorted tests whether a slice of strings is sorted in increasing order.
func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) }

我们可以直接使用这些方法对基本类型 slice 进行排序,如:

ids := []int{5,1,7,1,3,8,7,4}
names := []string{"qqq", "www", "ee", "aa", "rr", "ba"}sort.Ints(ids)
sort.Strings(names)fmt.Println(ids) // [1 1 3 4 5 7 7 8]
fmt.Println(names)  // [aa ba ee qqq rr www]

Golang interface 接口详解相关推荐

  1. 【java8新特性】——lambda表达式与函数式接口详解(一)

    一.简介 java8于2014年发布,相比于java7,java8新增了非常多的特性,如lambda表达式.函数式接口.方法引用.默认方法.新工具(编译工具).Stream API.Date Time ...

  2. RandomAccess接口详解

    RandomAccess接口详解 Interface RandomAccess All Known Implementing Classes: ArrayList, AttributeList, Co ...

  3. 【JavaWeb】Servlet系列——HttpServletRequest接口详解

    文章目录 23 HttpServletRequest接口详解 23.1 关于HttpServletRequest接口 23.2 HttpServletRequest接口的实现类谁写的? HttpSer ...

  4. 接口详解(JAVA)

    接口详解(JAVA) 文章目录 接口详解(JAVA) 接口 接口的定义和使用 练习 接口的细节:成员特点和接口的各种关系 接口中成员的特点 接口和类之间的关系 接口中新增方法,接口应用和适配器设计模式 ...

  5. I.MX8M mini物联网开发板框架及屏幕接口详解

    本文由用户robe.zhang发表于电路城论坛试用板块  主要内容: 1.imx8m mini 框架 2.核心板系统框架 3.开发板系统框架 4.屏幕接口详解 5.总结 1.imx8m mini 框架 ...

  6. Java接口 详解(二)

    上一篇Java接口 详解(一)讲到了接口的基本概念.接口的使用和接口的实际应用(标准定义).我们接着来讲. 一.接口的应用-工厂设计模式(Factory) 我们先看一个范例: package com. ...

  7. DP/HDMI/DVI显示器接口详解

    DP/HDMI/DVI接口区别 目前游戏竞技玩家,专业制图用户越来越多,这就导致了各大应用设备也不断的提高.用户对画面显示效果也同样高了不少,特别是影音用户和游戏玩家,他们对画质的要求要求都非常高,就 ...

  8. Python的C语言接口 - 详解官方文档

    Python的C语言接口 - 详解官方文档 索引 Python的C语言接口 - 详解官方文档 介绍 / Introduce 代码标准 / Coding Standards 包含文件 / Include ...

  9. Callable接口详解

    Callable接口详解 Callable: 返回结果并且可能抛出异常的任务. 优点: 可以获得任务执行返回值: 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果. Runnab ...

  10. Java6.0中Comparable接口与Comparator接口详解

    Java6.0中Comparable接口与Comparator接口详解 说到现在,读者应该对Comparable接口有了大概的了解,但是为什么又要有一个Comparator接口呢?难道Java的开发者 ...

最新文章

  1. JAVA单线程以及java多线程的实现方式
  2. 解决Clover在win 10下的兼容问题
  3. delphi 调用php接口_贝壳找房小程序从PHP到Golang的跃迁之路
  4. 数据库查询性能优化之利器—索引(二)
  5. Privatization of Roads in Treeland
  6. java -PDF添加文本水印与图片水印
  7. 人人都能掌握的Java服务端性能优化方案
  8. Netsparker超轻量级Web安全漏洞扫描工具使用教程介绍
  9. Android 系统性能优化(40)---Android LowMemoryKiller原理分析
  10. JAVA面试要点002_Git中fetch和pull的区别
  11. 华为MA5616配置及维护指南
  12. C# 随机数生成避免重复
  13. 【托马斯微积分】(12版)阅读笔记2:极限
  14. UML之Astah的基本使用教程-1
  15. 关于transition过渡的详解
  16. [UER#6 C]逃跑
  17. jquery循环获取div之间的内容
  18. 如何用真实图案填充图片?
  19. 3D打印技术新进展,正带来哪些产业新机会?
  20. 【ARM-8】MPIDR_EL1, Multiprocessor Affinity Register 多处理器关联寄存器

热门文章

  1. 三国志战略版鸿蒙梦魇,三国志战略版梦中弑臣厉害吗 梦中弑臣战法搭配
  2. 计算机病毒的特点分类危害性,计算机病毒的分类及破坏是什么
  3. python统计元音字母出现的次数,python统计元音字母个数 python输出元音字母
  4. 为什么要来学习算法?写在英雄的5月集训月末
  5. matlab蒙特卡洛模拟几何布朗,【数值模拟】几何布朗运动数值解的模拟
  6. 【算法面试题】工厂木材加工问题
  7. 360随身wifi2驱动 v5.3.0.1035 官方版
  8. 私有云和服务器虚拟化的区别,私有云和服务器有什么区别
  9. CentOS 根目录下目录介绍
  10. C/C++/Linux工程师学习资料干货路线这都有,从入门到实战!【CSDN宝藏资料图鉴第二期】