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 ...
随机推荐
- Java异常框架设计
什么是异常? 异常(exception)应该是异常事件(exceptional event)的缩写.异常定义:异常是一个在程序执行期间发生的事件,它中断正在执行的程序的正常的指令流.当在一个方法中发生 ...
- 描述J2EE框架的多层结构,并简要说明各层的作用。
描述J2EE框架的多层结构,并简要说明各层的作用. 解答: 1) Presentation layer(表示层) a. 表示逻辑(生成界面代码) b. 接收请求 c. 处理业务层抛出的异常 d. 负责 ...
- 【tyvj】P2065 「Poetize10」封印一击(贪心+线段树/差分)
http://new.tyvj.cn/p/2065 我就不说我很sb的用线段树来维护值...... 本机自测的时候想了老半天没想出怎么维护点在所有区间被多少区间包含的方法.最后一小时才想出来线段树(果 ...
- MathType输入补集符号的步骤有哪些
集合符号在很多的数学领域都会用到,其基本的集合运算可以分为交.并.补这三种.但是一些用户朋友们在编辑文档的时候想输入集合符号这个时候就需要用到数学公式编辑器MathType,但是很多人能够快速地编辑出 ...
- 基础-Eclipse 教程
1.Eclipse 是一个开放源代码的.基于 Java 的可扩展开发平台.2.下载地址为: https://www.eclipse.org/downloads/.3.Eclipse 修改字符集 : W ...
- Android多线程分析之中的一个:使用Thread异步下载图像
Android多线程分析之中的一个:使用Thread异步下载图像 罗朝辉 (http://blog.csdn.net/kesalin) CC 许可.转载请注明出处 打算整理一下对 Android Fr ...
- python bottle学习(四)request.quest/query_string/params/body等方法介绍
假设url:http://0.0.0.0:18082/api/cluster/group?wzd=111&abc=cc 方法类型:POST,body是{"name":& ...
- python之gevent模块实现协程
Python通过yield提供了对协程的基本支持,但是不完全.而第三方的gevent为Python提供了比较完善的协程支持. gevent是第三方库,通过greenlet实现协程,其基本思想是: 当一 ...
- python之进制转换
Python中二进制是以0b开头的: 例如: 0b11 则表示十进制的3 8进制是以0开头的: 例如: 011则表示十进制的9 16进制是以0x开头的: 例如: 0x11则表示十进制 ...
- Android实例-退出程序
Android实例-退出程序 http://www.cnblogs.com/FKdelphi unit Unit1; interface uses System.SysUtils, System.Ty ...