go——接口(二)
多态是指代码可以根据类型的具体实现采取不同行为的能力。
如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值。
标准库里有很好的例子,如io包里实现的流式处理接口。
io包提供了一组构造得非常好得接口和函数,来让代码轻松支持流式数据处理。
只要实现两个接口,就能利用整个io包背后得所有强大能力。
1.标准库
先来看一个示例程序,这个程序实现了流行程序curl得功能。
示例1:
//这个示例程序展示如何使用io.Reader和io.Writer接口
//写一个简单版本得curl程序
package main import (
"fmt"
"io"
"net/http"
"os"
) //init在main函数之前调用
func init() {
if len(os.Args) != 2 {
fmt.Println("Usage: ./example<url>")
os.Exit(-1)
}
} //main是应用程序得入口
func main() {
//从web服务器得到响应
r, err := http.Get(os.Args[1])
if err != nil {
fmt.Println(err)
range
} //从Body复制到Stdout
io.Copy(os.Stdout, r.Body)
if err := r.Body.Close(); err != nil {
fmt.Println(err)
}
}
上述代码展示了接口的能力以及在标准库里的应用。
只用了几行代码我们就通过两个函数以及配套的接口,完成了curl程序。
下面是部分代码的解释:
r, err := http.Get(os.Args[1])
调用了http包的get函数。在与服务器成功通信后,http.Get函数会返回一个http.Response类型的指针。
http.Response类型包含一个名为Body的字段,这个字段是一个io.ReadCloser接口类型的值。
io.Copy(os.Stdout, r.Body)
Body字段作为第二个参数传给io.Copy函数。io.Copy函数的第二个参数接受一个io.Reader接口类型的值,这个值表示数据流入的源。
Body字段实现了io.Reader接口,因此我们可以将Body字段传入io.Copy,使用Web服务器的返回内容作为源。
io.Copy的第一个参数是复制到目标,这个参数必须是一个实现了io.Writer接口的值。
对于这个目标,我们传入os包里的一个特殊值Stdout。这个接口值表示标准输出设备,并且已经实现了io.Writer接口。
当我们将Body和Stdout这两个值传给io.Copy函数后,这个函数会把服务器的数据分成小段,
源源不断地传给终端窗口,直到最后一个片段读取并写入到终端,io.Copy函数才返回。
io.Copy函数可以以这种工作流的方式处理很多标准库里已有的类型。
示例2:
//这个示例程序展示bytes.Buffer也可以
//用io.Copy函数
package main import (
"bytes"
"fmt"
"io"
"os"
) //main是应用程序的入口
func main() {
var b bytes.Buffer //将字符串写入Buffer
b.Write([]byte("Hello")) //将字符串拼接到Buffer
fmt.Fprintf(&b, "World!") //将Buffer的内容写到Stdout
io.Copy(os.Stdout, &b)
}
这个程序使用接口来拼接字符串,并将数据以流的方式输出到标准输出设备。
var b bytes.Buffer
创建一个bytes包里的Buffer类型的变量b,用于缓冲数据。
b.Write([]byte("Hello"))
使用Write方法将字符串Hello写入到缓冲区b。
fmt.Fprintf(&b, "World!")
调用fmt包里的Fprintf函数,将第二个字符串追加到缓冲区b里。
io.Copy(os.Stdout, &b)
将字符写道终端。
fmt.Fprintf函数接受一个io.Writer类型的接口值作为其第一个参数。
由于bytes.Buffer类型的指针实现了io.Writer接口,所以可以将缓存b传入fmt.Fprintf函数,并执行追加操作。
由于bytes.Buffer类型的指针也实现了io.Reader接口,io.Copy函数可以用于在终端窗口显式缓冲区b的内容。
2.实现
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。
如果用户定义的类型实现了某个接口声明的一组方法,那么这个用户定义的类型的值可以赋给这个接口类型的值。
这个赋值会把用户定义的类型的值存入接口类型的值。
对接口值方法的调用会执行接口值里存储的用户定义的类型的值对应的方法。
因为任何用户定义的类型都可以实现任何接口,所以对接口值方法的调用自然就是一种多态。
在这个关系里,用户定义的类型通常叫做实体类型,原因是如果离开了内部存储的用户定义的类型的值的实现,接口值并没有具体的行为。
并不是所有值都完全等同,用户定义的类型的值或者指针要满足接口的实现,需要遵守一些规则。

上图展示了在user类型值赋值后接口变量的值的内部布局。
接口值是一个两个字长度的数据结构,第一个字包含一个指向内部表的指针。
这个内部表叫做iTable,包含了所存储的值的类型信息。
iTable包含了已存储的值类型信息以及与这个值相关联的一组方法。
第二个字是一个指向所存储值得指针。将类型信息和指针组合在一起,就将这两个值组成了一种特殊得关系。

上图展示了一个指针赋值给接口之后发生的变化。
在这种情况里,类型信息会存储一个指向保存的类型的指针,而接口值第二个字依旧保存指向实体值的指针。
3.方法集
package main import (
"fmt"
) //notifier是一个接口定义
//通知类行为的接口
type notifier interface {
notify()
} //user在程序里定义了一个用户类型
type user struct {
name string
email string
} //notify是使用指针接收者实现的方法
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email)
} func main() {
//创建一个user类型的值,并发送通知
u := user{"Bill", "bill@email.com"} sendNotification(&u) }
//声明了一个函数,这个函数接收一个notifier接口类型的值
//之后使用这个接口值来调用notify方法。
//任何一个实现了notifier接口的值都可以传入sendNotification函数。
func sendNotification(n notifier) {
n.notify() //Sending user email to Bill<bill@email.com>
}
方法集定义了一组关联到给定类型的值或指针的方法。
定义方法时使用的接收者的类型决定了这个方法是关联到值还是关联到指针,还是两者都关联。

T类型的值的方法集只包含值接收者声明的方法。
而指向T类型的指针的方法集既包含值接收者声明的方法,也包含指针接收者声明的方法。
如果从接受者的视角来看。

如果使用执政接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。
如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口。
go——接口(二)的更多相关文章
- Hadoop序列化与Writable接口(二)
Hadoop序列化与Writable接口(二) 上一篇文章Hadoop序列化与Writable接口(一)介绍了Hadoop序列化,Hadoop Writable接口以及如何定制自己的Writable类 ...
- 【C#夯实】我与接口二三事:IEnumerable、IQueryable 与 LINQ
序 学生时期,有过小组作业,当时分工一人做那么两三个页面,然而在前端差不多的时候,我和另一个同学发生了争执.当时用的是简单的三层架构(DLL.BLL.UI),我个人觉得各写各的吧,到时候合并,而他觉得 ...
- C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解
系列目录 [已更新最新开发文章,点击查看详细] 在我的博客<C#二次开发BIMFACE系列61 File Management文件管理服务接口二次开发及实战详解>最后列出了 Fil ...
- java servlet手机app访问接口(二)短信验证
今天找了几个短信平台,其实最想使用的一个是sharesdk,使用它上面http api短信功能,不仅价格低,而且最少可以充值100RMB,但是审核过于严格,对应APP还必须集成他们的短信功能,而且要上 ...
- 一、HttpServletRequest接口 二、HttpServletReponse接口 三、POST和GET请求方式及其乱码处理 四、ServletContext对象和ServletConfig对象
一.HttpServletRequest接口 内部封装了客户端请求的数据信息 接收客户端的请求参数.HTTP请求数据包中配置参数 ###<1>常用方法 getContextPath()重要 ...
- java List接口二
一 ArrayList集合 ArrayList集合数据存储的结构是数组结构.元素增删慢,查找快,由于日常开发中使用最多的 功能为查询数据.遍历数据,所以ArrayList是最常用的集合. 许多程序员开 ...
- java 接口二
一 接口的多实现 接口最重要的体现:解决多继承的弊端.将多继承这种机制在java中通过多实现完成了. interface Fu1 { void show1(); } interface Fu2 { v ...
- Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)
写在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html 作者:yilezhu 上一篇关于Asp.Net Core Web Api图片上传的文章使 ...
- 对RedisTemplate接口二次封装成自定义工具接口
开发过程中,经常使用redis数据库存储. 一般都是依赖注入redisTemplate,然后调用redisTemplate的api进行接口功能实现. 但是,如果对redisTemplate自带的API ...
- go语言之接口二
接口查询: 先来看如下的结构.结构体File实现了Read,Writer,Seek,Close的方法 type File struct{ } func (f *File) Read(buf []byt ...
随机推荐
- VC编译的时候因为加载符号导致编译过程异常缓慢的处理
之前调试console程序用了一下Ctrl+C,弹出的异常处理选择框自己点了什么忘记了,结果之后再调试,发现特别慢,最下面一直在走着一系列dll的加载符号.让我特别二呼的是断网的话就没事了,联网就一直 ...
- C++ STL标准模板库(list)
//list的使用 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<list> using namesp ...
- [Django] 问题记录追踪表
关注的网站: https://simpleisbetterthancomplex.com/ Linux部署 1. Ubuntu下vsftpd安装部署 2. Utuntu下Django+Apache+W ...
- 请谈谈对SOA的认识。
请谈谈对SOA的认识. 解答:面向服务的体系结构(Service-Oriented Architecture,SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接 ...
- linux下的线程池
什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了.如果线程创建和销毁时间相比任 ...
- MySQL防止重复插入唯一限制的数据 4种方法
MySQL防止重复插入唯一限制的数据,下面我们逐一分析 : 1.insert ignore into 当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回.所以使用ignore请确保 ...
- js常用API汇总(转)
typeof(); 检测数据类型 String(); 转换成字符串 parseInt(); 解析出一个string或number的整数部分 parseFloat(); 解析出一个string的浮点数部 ...
- iOS-setNeedsLayout等布局方法
列举下iOS layout的相关方法: layoutSubviews layoutIfNeeded setNeedsLayout setNeedsDisplay drawRect sizeThatFi ...
- mvc中使用uploadify批量上传的应用
网上找了很多资料都没有发现一个好用.可以用的uploadify批量上传的应用,这里通过官方和自己的一些项目需要整理了一个出来. 希望能帮助到需要的人. 效果图:
- 一个页面从输入URL到页面加载完成发生了...待细化
一个页面从输入URL到页面加载完成发生了... 1.查找浏览器缓存 2.寻址:DNS解析 查找该域名对应的IP地址, 如果需要重定向(301),则再次发起请求 3. 进行HTTP协议会话 4.客户端发 ...