Go基础(七):接口

interface



Go 语言里面设计最精妙的应该算 interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被 interface 的巧妙设计所折服。

什么是 interface

简单的说,interface 是一组 method 签名的组合,我们通过 interface 来定义对象的一组行为。

我们前面一章最后一个例子中 Student 和 Employee 都能 SayHi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能 say hi

让我们来继续做更多的扩展,Student 和 Employee 实现另一个方法 Sing,然后 Student 实现方法 BorrowMoney 而 Employee 实现 SpendSalary。

这样 Student 实现了三个方法:SayHi、Sing、BorrowMoney;而 Employee 实现了 SayHi、Sing、SpendSalary。

上面这些方法的组合称为 interface (被对象 Student 和 Employee 实现)。例如 Student 和 Employee 都实现了 interface:SayHi 和 Sing,也就是这两个对象是该 interface 类型。而 Employee 没有实现这个 interface:SayHi、Sing 和 BorrowMoney,因为 Employee 没有实现 BorrowMoney 这个方法。

interface 类型

interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子

type Human struct {
name string
  age int
  phone string
}

type Student struct {
  Human // 匿名字段 Human
  school string
  loan float32
}

type Employee struct {
  Human // 匿名字段 Human
  company string
  money float32
}

// Human 对象实现 Sayhi 方法
func (h *Human) SayHi() {
  fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human 对象实现 Sing 方法
func (h *Human) Sing(lyrics string) {
  fmt.Println("La la, la la la, la la la la la...", lyrics)
}

// Human 对象实现 Guzzle 方法
func (h *Human) Guzzle(beerStein string) {
  fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee 重载 Human 的 Sayhi 方法
func (e *Employee) SayHi() {
  fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
  e.company, e.phone) // 此句可以分成多行
}

// Student 实现 BorrowMoney 方法
func (s *Student) BorrowMoney(amount float32) {
  s.loan += amount // (again and again and...)
}

// Employee 实现 SpendSalary 方法
func (e *Employee) SpendSalary(amount float32) {
  e.money -= amount // More vodka please!!! Get me through the day!
}

// 定义 interface
type Men interface {
  SayHi()
  Sing(lyrics string)
  Guzzle(beerStein string)
}

type YoungChap interface {
  SayHi()
  Sing(song string)
  BorrowMoney(amount float32)
}

type ElderlyGent interface {
  SayHi()
  Sing(song string)
  SpendSalary(amount float32)
}

通过上面的代码我们可以知道,interface 可以被任意的对象实现。我们看到上面的 Men interface 被 Human、Student 和 Employee 实现。同理,一个对象可以实现任意多个 interface,例如上面的 Student 实现了 Men 和 YoungChap 两个 interface。

最后,任意的类型都实现了空 interface (我们这样定义:interface {}),也就是包含 0 个 method 的 interface。

interface 值

那么 interface 里面到底能存什么值呢?如果我们定义了一个 interface 的变量,那么这个变量里面可以存实现这个 interface 的任意类型的对象。例如上面例子中,我们定义了一个 Men interface 类型的变量 m,那么 m 里面可以存 Human、Student 或者 Employee 值。

因为 m 能够持有这三种类型的对象,所以我们可以定义一个包含 Men 类型元素的 slice,这个 slice 可以被赋予实现了 Men 接口的任意结构的对象,这个和我们传统意义上面的 slice 有所不同。

让我们来看一下下面这个例子:

package main

import "fmt"

type Human struct {
  name string
  age int
  phone string
}

type Student struct {
  Human // 匿名字段
  school string
  loan float32
}

type Employee struct {
  Human // 匿名字段
  company string
  money float32
}

// Human 实现 SayHi 方法
func (h Human) SayHi() {
  fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human 实现 Sing 方法
func (h Human) Sing(lyrics string) {
  fmt.Println("La la la la...", lyrics)
}

// Employee 重载 Human 的 SayHi 方法
func (e Employee) SayHi() {
  fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
  e.company, e.phone)
}

// Interface Men 被 Human, Student 和 Employee 实现
// 因为这三个类型都实现了这两个方法
type Men interface {
  SayHi()
  Sing(lyrics string)
}

func main() {
  mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
  paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
  sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
  tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

  // 定义 Men 类型的变量i
  var i Men

  // i 能存储 Student
  i = mike
  fmt.Println("This is Mike, a Student:")
  i.SayHi()
  i.Sing("November rain")

  // i 也能存储 Employee
  i = tom
  fmt.Println("This is tom, an Employee:")
  i.SayHi()
  i.Sing("Born to be wild")

  // 定义了 slice Men
  fmt.Println("Let‘s use a slice of Men and see what happens")
  x := make([]Men, 3)
  // 这三个都是不同类型的元素,但是他们实现了 interface 同一个接口
  x[0], x[1], x[2] = paul, sam, mike

  for _, value := range x{
    value.SayHi()
  }
}

通过上面的代码,你会发现 interface 就是一组抽象方法的集合,它必须由其他非 interface 类型实现,而不能自我实现, Go 通过 interface 实现了 duck-typing: 即 "当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。

空 interface

空 interface (interface {}) 不包含任何的 method,正因为如此,所有的类型都实现了空 interface。空 interface 对于描述起不到任何的作用 (因为它不包含任何的 method),但是空 interface 在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于 C 语言的 void* 类型。

// 定义 a 为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a 可以存储任意类型的数值
a = i
a = s

一个函数把 interface {} 作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回 interface {}, 那么也就可以返回任意类型的值。是不是很有用啊!

interface 函数参数

interface 的变量可以持有任意实现该 interface 类型的对象,这给我们编写函数 (包括 method) 提供了一些额外的思考,我们是不是可以通过定义 interface 参数,让函数接受各种类型的参数。

举个例子:fmt.Println 是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开 fmt 的源码文件,你会看到这样一个定义:

type Stringer interface {
  String() string
}

也就是说,任何实现了 String 方法的类型都能作为参数被 fmt.Println 调用,让我们来试一试

package main
import (
  "fmt"
  "strconv"
)

type Human struct {
  name string
  age int
  phone string
}

// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
  return "?"+h.name+" - "+strconv.Itoa(h.age)+" years - ? " +h.phone+"?"
}

func main() {
  Bob := Human{"Bob", 39, "000-7777-XXX"}
  fmt.Println("This Human is : ", Bob)
}

现在我们再回顾一下前面的 Box 示例,你会发现 Color 结构也定义了一个 method:String。其实这也是实现了 fmt.Stringer 这个 interface,即如果需要某个类型能被 fmt 包以特殊的格式输出,你就必须实现 Stringer 这个接口。如果没有实现这个接口,fmt 将以默认的方式输出。

// 实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

注:实现了 error 接口的对象(即实现了 Error () string 的对象),使用 fmt 输出时,会调用 Error () 方法,因此不必再定义 String () 方法了。

interface 变量存储的类型

我们知道 interface 的变量里面可以存储任意类型的数值 (该类型实现了 interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

  • Comma-ok 断言

Go 语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里 value 就是变量的值,ok 是一个 bool 类型,element 是 interface 变量,T 是断言的类型。

如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false。

让我们通过一个例子来更加深入的理解。

package main

import (
  "fmt"
  "strconv"
)

type Element interface{}
type List [] Element

type Person struct {
  name string
  age int
}

// 定义了 String 方法,实现了 fmt.Stringer
func (p Person) String() string {
  return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

func main() {
  list := make(List, 3)
  list[0] = 1 // an int
  list[1] = "Hello" // a string
  list[2] = Person{"Dennis", 70}

  for index, element := range list {
    if value, ok := element.(int); ok {
      fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
    } else if value, ok := element.(string); ok {
      fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
    } else if value, ok := element.(Person); ok {
      fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
    } else {
      fmt.Printf("list[%d] is of a different type\n", index)
    }
  }
}

是不是很简单啊,同时你是否注意到了多个 if 里面,还记得我前面介绍流程时讲过,if 里面允许初始化变量。

也许你注意到了,我们断言的类型越多,那么 if else 也就越多,所以才引出了下面要介绍的 switch。

switch 测试

最好的讲解就是代码例子,现在让我们重写上面的这个实现

package main

import (
  "fmt"
  "strconv"
)

type Element interface{}
type List [] Element

type Person struct {
  name string
  age int
}

// 打印
func (p Person) String() string {
  return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

func main() {
  list := make(List, 3)
  list[0] = 1 // an int
  list[1] = "Hello" // a string
  list[2] = Person{"Dennis", 70}

  for index, element := range list{
    switch value,ok := element.(type) {
    case int:
      fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
    case string:
      fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
    case Person:
      fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
    default:
      fmt.Printf("list[%d] is of a different type", index)
    }
  }
}

这里有一点需要强调的是:element.(type) 语法不能在 switch 外的任何逻辑里面使用,如果你要在 switch 外面判断一个类型就使用 comma-ok。

嵌入 interface

Go 里面真正吸引人的是它内置的逻辑语法,就像我们在学习 Struct 时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到 interface 里面,那不是更加完美了。如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1 里面的 method。

我们可以看到源码包 container/heap 里面有这样的一个定义

type Interface interface {
  sort.Interface // 嵌入字段 sort.Interface
  Push(x interface{}) // a Push method to push elements into the heap
  Pop() interface{} // a Pop elements that pops elements from the heap
}

我们看到 sort.Interface 其实就是嵌入字段,把 sort.Interface 的所有 method 给隐式的包含进来了。也就是下面三个方法:

type Interface interface {
  // Len is the number of elements in the collection.
  Len() int
  // Less returns 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)
}

另一个例子就是 io 包下面的 io.ReadWriter ,它包含了 io 包下面的 Reader 和 Writer 两个 interface:

// io.ReadWriter
type ReadWriter interface {
  Reader
  Writer
}

原文地址:https://www.cnblogs.com/mxsf/p/12679466.html

时间: 2024-08-01 11:10:21

Go基础(七):接口的相关文章

网络基础七层原理(重点)

网络基础七层原理一.什么是网络基础七层原理二.七层模型的起源三.七层模型的原理和协议四.七层模型有何用处1.七层模型,亦称OSI(Open System Interconnection).参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一般称为OSI参考模型或七层模型.它是一个七层的.抽象的模型体,不仅包括一系列抽象的术语或概念,也包括具体的协议.2.OSI的大部分设计工作实际上只是Honeywell Information System 公司的一个小组完成的,

Java基础十--接口

Java基础十--接口 一.接口的定义和实例 1 /* 2 abstract class AbsDemo 3 { 4 abstract void show1(); 5 abstract void show2(); 6 } 7 8 当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用 9 另一种形式定义和表示,就是 接口 interface. 10 */ 11 12 //定义接口使用的关键字不是class,是interface. 13 //interface编译后还是.class文件,其实本质

[.net 面向对象编程基础] (16) 接口

[.net 面向对象编程基础] (16) 接口 关于“接口”一词,跟我们平常看到的电脑的硬件“接口”意义上是差不多的.拿一台电脑来说,我们从外面,可以看到他的USB接口,COM接口等,那么这些接口的目的一就是让第三方厂商生产的外设都有相同的标准,也是提供一个对外通信或操作的入口. 只是C#的接口除了以上特点之外,还具有一种类似于模板的功能,我们定义一组接口,就像是一个模板.这点和抽象类不同,抽象类是先有子类或都子类的概念,从中抽象出一个类.而接口更像是我们要设计一台机器,先把这台机器对外的功能接

Java基础10 接口的继承与抽象类(转载)

接口继承 接口继承(inheritance)与类继承很类似,就是以被继承的interface为基础,增添新增的接口方法原型.比如,我们以Cup作为原interface: interface Cup{    void addWater(int w);    void drinkWater(int w);} 我们在继承Cup的基础上,定义一个新的有刻度的杯子的接口,MetricCup 接口如下: interface MetricCup extends Cup{    int WaterContent

黑马程序员_Java基础_接口

------- android培训.java培训.期待与您交流! ---------- 0.接口知识体系 Java接口的知识体系如下图所示,掌握下图中的所有知识就可精通接口. 1.接口概论 1)接口概念 接口是从多个相似类中抽象出来的规范,接口中不包含普通方法,所有方法都是抽象方法,接口不提供实现.接口体现的是规范和实现分离的哲学.规范和实现分离正是接口的好处,让软件系统的各个组件之间面向接口耦合,是一种松耦合的设计.接口定义的是多个类共同的公共行为规范,定义的是一组公用方法. 2)接口与抽象类

spring中基础核心接口总结

spring中基础核心接口总结理解这几个接口,及其实现类就可以快速了解spring,具体的用法参考其他spring资料 1.BeanFactory最基础最核心的接口重要的实现类有:XmlBeanFactory,以及ApplicationContext接口下的类 2.Resource接口,可以通用地访问文件资源1)ClassPathResource:读取得形式为"classpath:ApplicationContext.xml"2)FileStstemResource:读取得形式为&qu

C#夯实基础之接口(《CLR via C#》读书笔记)

一. 接口的类型 接口是引用类型.因此从值类型赋值给接口是需要装箱的.如下所示: 1 class Program 2 { 3 static void Main(string[] args) 4 { 5 ISay catSay = new Cat(); 6 catSay.Say(); 7 Console.Read(); 8 } 9 } 10 11 interface ISay 12 { 13 void Say(); 14 } 15 struct Cat : ISay 16 { 17 public

09.Django基础七之Ajax

一 Ajax简介 1.简介 AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步的Javascript和XML".即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据). AJAX 不是新的编程语言,而是一种使用现有标准的新方法. AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容.(这一特点给用户的感受是在不知不觉中完成请求和响应

Django基础七之Ajax

目录 一 Ajax简介 1.简介 2.示例 3.AJAX常见应用情景 4.AJAX的优缺点 二 Ajax的使用 1.基于jQuery的实现 2.基于原生js实现 3.Ajax-服务器-Ajax流程图 三 Ajax请求设置csrf_token 方式1 方式2 四 Ajax文件上传 请求头ContentType 1 application/x-www-form-urlencoded(看下图) 2 multipart/form-data 3 application/json 基于form表单的文件上传