强调一下是我个人的见解以及接口在 Go 语言中的意义。

如果您写代码已经有了一段时间,我可能不需要过多解释接口所带来的好处,但是在深入探讨 Go 语言中的接口前,我想花一两分钟先来简单介绍一下接口。 如果您对接口很熟悉,请先跳过下面这段。

接口的简单介绍

在任一编程语言中,接口——方法或行为的集合,在功能和该功能的使用者之间构建了一层薄薄的抽象层。在使用接口时,并不需要了解底层函数是如何实现的,因为接口隔离了各个部分(划重点)。

跟不使用接口相比,使用接口的最大好处就是可以使代码变得简洁。例如,您可以创建多个组件,通过接口让它们以统一的方式交互,尽管这些组件的底层实现差异很大。这样就可以在编译甚至运行的时候动态替换这些组件。

用 Go 的 io.Reader 接口举个例子。io.Reader 接口的所有实现都有 Read(p []byte) (n int, err error) 函数。使用 io.Reader 接口的使用者不需要知道使用这个 Read 函数的时候那些字节从何而来。

具体到 Go 语言

在我使用 Go 语言的过程中,与我使用过的其他任何编程语言相比,我经常发现其他的、不那么明显的使用接口的原因。今天,我将介绍一个很普遍的,也是我遇到了很多次的使用接口的原因。

Go 语言没有构造函数

很多编程语言都有构造函数。构造函数是定义自定义类型(即 OO 语言中的类)时使用的一种建立对象的方法,它可以确保必须执行的任何初始化逻辑均已执行。

例如,假设所有 widgets 都必须有一个不变的,系统分配的标识符。在 Java 中,这很容易实现:

package io.krancour.widget;

import java.util.UUID;

public class Widget {

    private String id;

    // 使用构造函数初始化
public Widget() {
id = UUID.randomUUID().toString();
} public String getId() {
return id;
}
}
class App {
public static void main( String[] args ){
Widget w = new Widget();
System.out.println(w.getId());
}
}

从上面这个例子可以看到,没有执行初始化逻辑就无法实例化一个新的 Widget

但是 Go 语言没有此功能。

在 Go 语言中,可以直接实例化一个自定义类型。

定义一个 Widget 类型:

package widgets

type Widget struct {
id string
} func (w Widget) ID() string {
return w.id
}

可以像这样实例化和使用一个 widget

package main

import (
"fmt"
"github.com/krancour/widgets"
) func main() {
w := widgets.Widget{}
fmt.Println(w.ID())
}

如果运行此示例,那么(也许)意料之中的结果是,打印出的 ID 是空字符串,因为它从未被初始化,而空字符串是字符串的“零值”。 我们可以在 widgets 包中添加一个类似于构造函数的函数来处理初始化:

package widgets

import uuid "github.com/satori/go.uuid"

type Widget struct {
id string
} func NewWidget() Widget {
return Widget{
id: uuid.NewV4().String(),
}
} func (w Widget) ID() string {
return w.id
}

然后我们简单地修改 main 来使用这个类似于构造函数的新函数:

package main

import (
"fmt"
"github.com/krancour/widgets"
) func main() {
w := widgets.NewWidget()
fmt.Println(w.ID())
}

执行该程序,我们得到了想要的结果。

但是仍然存在一个严重问题!我们的 widgets 包没有强制用户在初始一个 widget 的时候使用我们的构造函数。

变量私有化

首先我们尝试把自定义类型的变量私有化,以此来强制用户使用我们规定的构造函数来初始化 widget。在 Go 语言中,类型名、函数名的首字母是否大写决定它们是否可被其他包访问。名称首字母大写的可被访问(也就是 public ),而名称首字母小写的不可被访问(也就是 private )。所以我们把类型 Widget 改为类型 widget

package widgets

import uuid "github.com/satori/go.uuid"

type widget struct {
id string
} func NewWidget() widget {
return widget{
id: uuid.NewV4().String(),
}
} func (w widget) ID() string {
return w.id
}

我们的 main 代码保持不变,这次我们得到了一个 ID 。这比我们想要的要近了一步,但是我们在此过程中犯了一个不太明显的错误。类似于构造函数的 NewWidget 函数返回了一个私有的实例。尽管编译器对此不会报错,但这是一种不好的做法,下面是原因解释。

在 Go 语言中,包*是复用的基本单位。其他语言中的类*是复用的基本单位。如前所述,任何无法被外部访问的内容实质上都是“包私有”,是该包的内部实现细节,对于使用这个包的使用者来说不重要。因此,Go 的文档生成工具 godoc 不会为私有的函数、类型等生成文档。

当一个公开的构造函数返回一个私有的 widget 实例,实际上就陷入了一条死胡同。调用这个函数的人哪怕有这个实例,也绝对在文档里找不到任何关于这个实例类型的描述,也更不知道 ID() 这个函数。Go 社区非常重视文档,所以这样做是不会被接受的。

轮到接口上场了

回顾一下,到目前为止,我们写了一个类似于构造函数的函数来解决 Go 语言缺乏构造函数的问题,但是为了确保人们用该函数而不是直接实例化 Widget ,我们更改了该类型的可见性——将其重命名为 widget,即私有化了。虽然编译器不会报错,但是文档中不会出现对这个私有类型的描述。不过,我们距离想要的目标还近了一步。接下来就要使用接口来完成后续的了。

通过创建一个*可被访问的*widget 类型可以实现的接口,我们的构造函数可以返回一个公开的类型实例,并且会显示在 godoc 文档中。同时,这个接口的底层实现依然是私有的,使用者无法直接创建一个实例。

package widgets

import uuid "github.com/satori/go.uuid"

// Widget is a ...
type Widget interface {
// ID 返回这个 widget 的唯一标识符
ID() string
} type widget struct {
id string
} // NewWidget() 返回一个新的 Widget 实例
func NewWidget() Widget {
return widget{
id: uuid.NewV4().String(),
}
} func (w widget) ID() string {
return w.id
}

总结

我希望我已经充分地阐述了 Go 语言的这一特质——构造函数的缺失反而促进了接口的使用。

在我的下一篇文章中,我将介绍一种几乎与之相反的场景——在其他语言中要使用接口但是在 Go 语言中却不必。


via: https://medium.com/@kent.rancourt/go-pointers-why-i-use-interfaces-in-go-338ae0bdc9e4

作者:Kent Rancourt 译者:zhiyu-tracy-yang 校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

在 Go 语言中,我为什么使用接口的更多相关文章

  1. C语言中,头文件和源文件的关系(转)

    简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句, ...

  2. C语言中的static 详细分析

    转自:http://blog.csdn.net/keyeagle/article/details/6708077/ google了近三页的关于C语言中static的内容,发现可用的信息很少,要么长篇大 ...

  3. C语言中.h和.c文件解析(很精彩)

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析 ...

  4. C语言中.h和.c文件解析

    整理自C语言中.h和.c文件解析(很精彩) Part.1(林锐<高质量C/C++编程>) 通过头文件来调用库功能.在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的 ...

  5. Java 语言中 Enum 类型的使用介绍

    Enum 类型的介绍 枚举类型(Enumerated Type) 很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中.而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常 ...

  6. C语言中的static 具体分析

    google了近三页的关于C语言中static的内容,发现可用的信息非常少,要么长篇大论不知所云要么在关键之处几个字略过,对于想挖掘底层原理的刚開始学习的人来说參考性不是非常大.所以,我这篇博文博採众 ...

  7. 【翻译】go语言中的map实战

    业余时间翻译,水平很差,如有瑕疵,纯属无能. 原文链接 http://blog.golang.org/go-maps-in-action go语言中的map实战 1. 简介 哈希表是计算机科学中最重要 ...

  8. 转-C语言中.h和.c文件解析

    C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:       1.预处理阶段 2.词 ...

  9. [转]理解Go语言中的nil

    最近在油管上面看了一个视频:Understanding nil,挺有意思,这篇文章就对视频做一个归纳总结,代码示例都是来自于视频. nil是什么 相信写过Golang的程序员对下面一段代码是非常非常熟 ...

随机推荐

  1. JS内存机制

    在看JS内存机制之前我们先来看一下JS是门什么样的语言,他又有哪些变量类型. 动静态,强弱类型 静态:在使用之前就需要确认其变量数据类型. 动态:在运行过程中需要检查数据类型. 强类型:不支持隐式类型 ...

  2. 一张PDF了解JDK11 GC调优秘籍-附PDF下载

    目录 简介 废弃的VM选项 Source-File Mode Code Heap状态分析 AppCDS 总结 简介 JDK11相比JDK10,添加了一个新的Source-File Mode,可以直接通 ...

  3. 分布式锁(3) ----- 基于zookeeper的分布式锁

    分布式锁系列文章 分布式锁(1) ----- 介绍和基于数据库的分布式锁 分布式锁(2) ----- 基于redis的分布式锁 分布式锁(3) ----- 基于zookeeper的分布式锁 代码:ht ...

  4. Presto 函数开发

    0. 写在前面 Presto Functions 并不能像 Hive UDF 一样动态加载,需要根据 Function 的类型,实现 Presto 内部定义的不同接口,在 Presto 服务启动时进行 ...

  5. React Navigation / React Native Navigation 多种类型的导航结合使用,构造合理回退栈

    React Navigation 更新到版本5已经是非常完善的一套导航管理组件, 提供了Stack , Tab , Drawer 导航方式 , 那么我们应该怎样设计和组合应用他们来构建一个完美的回退栈 ...

  6. # SpringBoot-环境搭建

    SpringBoot-环境搭建 标签(空格分隔): java,SpringBoot 1.创建Maven工程 2.编写pom文件 <parent> <groupId>org.sp ...

  7. Redis服务之常用配置(二)

    上一篇博客我们聊了下redis的INCLUDE.NETWORK.GENERAL配置段相关配置和说明,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/133831 ...

  8. Servlet学习之Tomcat secretRequired配置报错问题

    Tomcat secretRequired配置问题 在启动Tomcat时,控制台可能会出现IllegalArgumentException异常,即"AJP连接器配置secretRequire ...

  9. BSOJ 5553 wangyurzee的树 prufer序列 容斥

    BSOJ我也不知道在哪. 容易想到容斥. 考虑不合法的方案 想到强制某个点的度数为限制即可. 这样就变成了了总方案-一个不合法+两个不合法-3个......的模型了. 坑点 当强制两个相同的点时 方案 ...

  10. QDC day4

    图论. 强连通图 与 弱连通图 . 最短路 .dij 不支持负权.显然 值得一提的是利用斐波那契堆m+nlogn . 一张 边权都是2的整数次幂 考虑 一下直接 结构体维护这个2的整次幂数组但比大小 ...