依据项目系统的面向对象编程语言Go

(整理起网络)

面向对象编程

Go语言的面向对象编程(OOP)非常简单而雅致。说它简洁,在于她从未了OOP中众概念,比如:继承、虚函数、构造函数和析构函数、隐藏的this指针等等。说她优雅,是其的面向对象(OOP)是语言类系统(type
system)中之原貌之如出一辙部分。整个项目系统通过接口(interface)串联,浑然一体。

品类系统(type system)

非常少生编程类的书谈及类型系统(type
system)这个话题。但骨子里品种系统是全部语言的支撑,至关重要。

花色系统(type
system)是指一个言语的色体系图。在普项目体系图被,包含这些情节:

  • 主导类型。如byte、int、bool、float等等。
  • 复合类型。如数组(array)、结构体(struct)、指针(pointer)等。
  • Any类型。即好对任意对象的档次。
  • 值语义和援语义。
  • 面向对象。即所有具有面向对象特征(比如来成员方法)的花色。
  • 接口(interface)。

类系统(type
system)描述的是这些内容在一个言语中哪些给波及。比如我们聊聊Java的花色系统:在Java语言中,存在个别仿照了独立的种系统,一仿是值类型系统,主要是中心类型,如byte、int、boolean、char、double、String等,这些品种因值语义。一拟是盖Object类型为根的目标类型系统,这些品种可以定义成员变量、成员方法、可以来虚函数。这些品种因引用语义,只同意new出来(只允许在堆上)。只有靶类型系统受到之实例可以被Any类型引用。Any类型就是任何对象类型系统的根
——
Object类型。值类型想如果被Any类型引用,需要装箱(Boxing)过程,比如int类型需要装箱成为Integer类型。只有靶类型系统遭到之花色才好实现接口(方法是吃该项目从如贯彻的接口继承)。

当Go语言中,多数种且是值语义,并且都得产生方法。在用之时节,你可给另外类型(包括内置类型)“增加”新方式。实现有接口(interface)无需由该接口继承(事实上Go语言并无持续语法),而只需要贯彻该接口要求的有术。任何项目且得被Any类型引用。Any类型就是空接口,亦即
interface{}。

深受项目增加方法

于Go语言中,你可被自由档次(包括内置类型,但指针类型除外)增加方法,例如:

type Integer int

func (a Integer) Less(b Integer) bool {
    return a < b
}

当此事例中,我们定义了一个新类型Integer,它同int没有本质不同,只是其呢搭的int类型增加了个新措施:Less。如此,你便可给整型看起如个类那样用:

func main() {
    var a Integer = 1
    if a.Less(2) {
        fmt.Println(a, "Less 2")
    }
}

每当拟其他语言的时光,很多初家针对面向对象感到很神秘。我于受新家介绍面向对象的时段,经常说及“面向对象只是一个语法糖”。以上代码用面向过程的方来形容是这般的:

type Integer int

func Integer_Less(a Integer, b Integer) bool {
return a < b
}

func main() {
var a Integer = 1
if Integer_Less(a, 2) {
    fmt.Println(a, "Less 2")
}
}

在Go语言中,面向对象的地下面纱被剖开得千篇一律干二全都。对比就简单段代码:

func (a Integer) Less(b Integer) bool {  // 面向对象
    return a < b
}

func Integer_Less(a Integer, b Integer) bool {  // 面向过程
    return a < b
}

a.Less(2)  // 面向对象
Integer_Less(a, 2)  // 面向过程

您得视,面向对象只是易了平种植语法形式来表述。在Go语言中从不藏身的this指针。这句话的意义是:

第一,方法施加的对象(也就是是“对象”)显式传递,没有于藏起来。
次,方法施加的靶子(也便是“对象”)不欲不得是指针,也未用不得被this。

咱俩对比Java语言的代码:

class Integer {
    private int val;
    public boolean Less(Integer b) {
        return this.val < b.val;
    }
}

及时段Java代码初家会比较为难理解,主要是盖Integer类的Less方法隐藏了第一单参数Integer*
this。如果用其翻译成C代码,会再次鲜明:

struct Integer {
    int val;
};

bool Integer_Less(Integer* this, Integer* b) {
    return this->val < b->val;
}

当Go语言中的面向对象最为直观,也任需付出额外的本钱。如果要求对象要坐指针传递,这有时会是个额外资产,因为对象有时坏有点(比如4独字节),用指针传递并无经济。

只有当公待修改对象的时光,才须用指针。它不是Go语言的格,而是相同种植自然约束。举个例子:

func (a *Integer) Add(b Integer) {
    *a += b
}

此地呢Integer类型增加了Add方法。由于Add方法需要改对象的值,所以需要因此指针引用。调用如下:

func main() {
    var a Integer = 1
a.Add(2)
    fmt.Println("a =", a)
}

运作该次获取的结果是:a = 3。如果您不要指针:

func (a Integer) Add(b Integer) {
    a += b
}

运作程序取得的结果是:a =
1,也便是涵养原来的值。究其原因,是为Go和C语言一样,类型且是因值传递。要惦记改变量的价值,只能传递指针。

值语义和援语义

值语义和援语义的出入在赋值:

b = a
b.Modify()

万一b的修改不见面影响a的价,那么此类型属于值类型。如果会影响a的价值,那么此类型是援引类型。

多数Go语言中之种,包括:

  • 核心项目。如byte、int、bool、float32、float64、string等等。
  • 复合类型。如数组(array)、结构体(struct)、指针(pointer)等。

且冲值语义。Go语言中项目的值语义表现得要命干净。我们如此说是因为数组(array)。如果您上了C语言,你会掌握C语言中之数组(array)比较特别。通过函数传递一个数组的时节因引用语义,但是在结构体中定义数组变量的早晚是值语义(表现在结构体赋值的时光,该数组会被完整地拷贝一卖新的副本)。

Go语言中之数组(array)和中坚型没有区别,是杀纯粹的值类型。例如:

var a = [3]int{1, 2, 3}
var b = a
b[1]++
fmt.Println(a, b)

程序运行结果:[1 2 3] [1 3 3]。这表明b =
a赋值语句是数组内容的一体化拷贝。要想发挥引用,需要用指针:

var a = [3]int{1, 2, 3}
var b = &a
b[1]++
fmt.Println(a, *b)

程序运行结果:[1 3 3] [1 3
3]。这标志b=&a赋值语句是数组内容之援。变量b的色不是[3]int,而是*[3]int类型。

Go语言中来4个档次比较特别,看起如引用类型:

  • 片(slice):指向数组(array)的一个区间。
  • 字典(map):极其普遍的数据结构,提供key-value查询能力。
  • 通道(chan):执行体(goroutine)间通讯装置。
  • 接口(interface):对同样组满足某个契约的类的虚幻。

而这并无影响我们以Go语言类型是值语义的本色。我们一个个来拘禁这些类别:

切开(slice)本质上是range,你可以大致将 []T 表示为:

type slice struct {
    first *T
    last *T
    end *T
}

盖切片(slice)内部是平等雨后春笋之指针,所以可以转移所指向的数组(array)的素并无奇怪。slice类型本身的赋值仍然是值语义。

字典(map)本质上是一个字典指针,你可以大致将map[K]V表示为:

type Map_K_V struct {
    ...
}

type map[K]V struct {
    impl *Map_K_V
}

据悉指针(pointer),我们一齐可以从定义一个援类型,如:

type IntegerRef struct { impl *int }

大路(chan)和字典(map)类似,本质上是一个指南针。为什么将她们计划啊凡援类型而非是联合之值类型,是以完全拷贝一个通路(chan)或字典(map)不是例行需求。

一如既往,接口(interface)具备引用语义,是坐中间维持了片只指针。示意为:

type interface struct {
    data *void
    itab *Itab
}

接口在Go语言中的地位十分主要。关于接口(interface)内部贯彻细节,后面在高阶话题受到,我们还细小分析。

结构体(struct)

Go语言的结构体(struct)和任何语言的类(class)有雷同的身价。但Go语言放弃了包连续在内的雅量OOP特性,只保留了组合(compose)这个极端基础之性状。

重组(compose)甚至不可知算是OOP的特色。因为连C语言如此的过程式编程语言中,也闹结构体(struct),也闹做(compose)。组合只是形成复合类型的根底。

上面我们说及,所有的Go语言的种类(指针类型除外)都是好发谈得来的主意。在是背景下,Go语言的结构体(struct)它只是坏平凡的复合类型,平淡无奇。例如我们设定义一个矩形类型:

type Rect struct {
    x, y float64
    width, height float64
}

接下来我们定义方法Area来测算矩形的面积:

func (r *Rect) Area() float64 {
    return r.width * r.height
}

初始化

概念了Rect类型后,我们怎么样创造并初始化Rect类型的对象实例?有如下方法:

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

每当Go语言中,未显式进行初始化的变量,都见面初始化为该种的零值(例如对于bool类型的零值为false,对于int类型零值为0,对于string类型零值为空字符串)。

构造函数?不待。在Go语言中君独自需要定义一个便的函数,只是普通因为NewXXX来定名,表示“构造函数”:

func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}

顿时整个充分自然,没有另外突兀的处在。

匿名组合

适龄地说,Go语言也供了延续,但是用了整合的文法,我们称为匿名组合:

type Base struct {
    ...
}

func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }

type Foo struct {
    Base
    ...
}

func (foo *Foo) Bar() {
    foo.Base.Bar()
    ...
}

如上代码定义了一个Base类(实现了Foo、Bar两只分子方法),然后定义了一个Foo类,从
Base“继承”并实现了改写了Bar方法,该方式实现时优先调用了基类的Bar方法。

每当“派生类”Foo没有转写“基类”Base的积极分子方法时,相应的艺术就给“继承”。例如在面的例证中,调用foo.Foo()
和调用foo.Base.Foo() 效果一样。

分为任何语言,Go语言很清楚地告知你类的内存布局是什么的。在Go语言中您还好随心所欲地修改外存布局,如:

type Foo struct {
...
    Base
}

立段代码从语义上的话,和地方给例子并无两样,但内存布局有了改。“基类”Base的数量被在了“派生类”Foo
的末梢。

另外,在Go语言中而还可以因指针方式自一个好像“派生”:

type Foo struct {
    *Base
    ...
}

眼看段Go代码仍然发生“派生”的成效,只是Foo创建实例的下,需要外表提供一个Base类实例的指针。C++
中其实呢生相近之法力,那就是是虚基类。但是虚基类是那个让人口难以掌握的特点,普遍达到来说
C++ 的开发者都见面忘记这个特性。

分子的但访问性

Go语言对主要字之增加非常吝啬。在Go语言中莫private、protected、public这样的重要性字。要想有符号而给另外保险(package)访问,需要将拖欠符号定义为充分写字母开头。如:

type Rect struct {
    X, Y float64
    Width, Height float64
}

然,Rect类型的成员变量就整受public了。成员方法按千篇一律的规则,例如:

func (r *Rect) area() float64 {
    return r.Width * r.Height
}

这么,Rect的area方法只有能够于拖欠种所在的担保(package)内用。

消强调的少数凡,Go语言中符号的可是访问性是管(package)一级的,而无是近似一级的。尽管area是Rect的中间方法,但是当与一个包中的其它类可以拜到它。这样的只是访问性控制甚粗旷,很特别,但是好实用。如果Go语言符号的可访问性是近乎一级的,少不了还要长friend这样的要字,以象征两单近乎是冤家干,可以看中的私有成员。

接口(interface)

Rob
Pike已说,如果不得不挑一个Go语言的特色移植到其它语言中,他会晤挑接口。

接口(interface)在Go语言有着重大的位置。如果说goroutine和channel
是支撑起Go语言的面世模型的木本,让Go语言在今天集群化与多核化的秋,成为同多亮丽的景物;那么接口(interface)是Go语言整个项目系统(type
system)的本,让Go语言在基础编程哲学的探索达到,达到史无先例的可观。

自我都于差不多只场所说,Go语言在编程哲学上是换革派,而无是改良派。这不是坐Go语言有
goroutine和channel,而还关键之是因Go语言的品种系统,因为Go语言的接口。因为有接口,才给Go语言的编程哲学变得全面。

Go 语言的接口(interface)不单单只是只是是接口。

怎么这么说?让咱们细细道来。

其他语言(C++/Java/C#)的接口

Go语言的接口,并无是您前面以其它语言(C++/Java/C#当)中接触到之接口。

每当Go语言之前的接口(interface),主要作不同组件之间的契约存在。对契约的贯彻是挟持的,你不能不声明你实在实现了拖欠接口。为了贯彻一个接口,你用打该接口继承:

interface IFoo {
    void Bar();
}

class Foo implements IFoo { // Java 文法
    ...
}

class Foo : public IFoo { // C++ 文法
...
}

IFoo* foo = new Foo;

纵然另外在一个一如既往的接口,只是名字不同为IFoo2(名字如出一辙可于不同的名字空间下,也是名不同),上面的类Foo只兑现了IFoo,但从没兑现IFoo2。

随即类似接口(interface),我们称为侵入式的接口。“侵入式”的重要性表现在实现类似需要鲜明宣示自己实现了某个接口。

这种强制性的接口继承,是面向对象编程(OOP)思想升华历程遭到的一个重要失误。我因此这么讲,是因其从根本上是背事物之因果报应关系的。

吃咱于契约的朝三暮四过程谈起。设想我们本一旦落实一个略搜索引擎(SE)。该找引擎需要依靠两只模块,一个是哈希表(HT),一个凡是HTML分析器(HtmlParser)。

觅引擎的实现者认为,SE对哈希表(HT)的依赖是显眼的,所以他无并觉得需要以SE和HT之间定义接口,而是一直import(或者include)的方法利用了HT;而模块SE对HtmlParser的因是未确定的,未来恐得发WordParser、PdfParser等模块来替代HtmlParser,以达成不同之工作要求。为者,他定义了SE和HtmlParser之间的接口,在模块SE中经接口调用方式间接引用模块HtmlParser。

该注意到,接口(interface)的需求方是找引擎(SE)。只有SE才明白接口应该定义成什么则才比较逾客观。但是接口的实现方是HtmlParser。基于模块设计之仅为据原则,模块HtmlParser实现自身之事体时,不应关心某个具体而用方的渴求。HtmlParser在落实的时候,甚至还非清楚未来发出同等龙SE会用上她。
要求模块HtmlParser知道有它的需求方的需的接口,并提前声明实现了这些接口是无客观的。同样的理发生在物色引擎(SE)自己身上。SE并无可知预测未来会有怎样需求方需要用到好,并且实现他们所求的接口。

夫题材在标准库的供来说,变得更加突出。比如我们落实了File类(这里我们用Go语言的文法来描述如促成的方法,请忽略文法上的细节),它发生这些方法:

Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error

那么,到底是理所应当定义一个IFile接口,还是应定义一文山会海的IReader, IWriter,
ISeeker,
ICloser接口,然后叫File从她们连续好吧?脱离了事实上的用户场景,讨论即简单只统筹哪个还好并任意义。问题在,实现File类的时刻,我岂知道外部会什么用她也?

正巧因为这种无客观的宏图,使得Java、C# 的类库每个接近实现之时段都需要纠结:

  • 题目1:我提供什么接口好为?
  • 题目2:如果简单单近乎实现了一如既往之接口,应该将接口放到哪个包好吧?

非侵入式接口

在Go语言中,一个接近才待贯彻了接口要求的有函数,那么我们就说此类似实现了拖欠接口。例如:

type File struct {
    ...
}

func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error

此间我们定义了一个File类,并落实有Read,Write,Seek,Close等方式。设想我们发出如下接口:

type IFile interface {
    Read(buf []byte) (n int, err error)
    Write(buf []byte) (n int, err error)
    Seek(off int64, whence int) (pos int64, err error)
    Close() error
}

type IReader interface {
    Read(buf []byte) (n int, err error)
}

type IWriter interface {
    Write(buf []byte) (n int, err error)
}

type ICloser interface {
    Close() error
}

尽管File类并不曾起这些接口继承,甚至可以免明白这些接口的存在,但是File类实现了这些接口,可以进行赋值:

var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)

Go语言的非侵入式接口,看似只是做了大粗之文法调整,但其实影响深远。

本条,Go语言的标准库,再为不需要绘制类库的后续树图。你早晚见了很多C++、Java、C#
类库的存续树图。这里让个Java继承树图:

http://docs.oracle.com/javase/1.4.2/docs/api/overview-tree.html

当Go中,类的继承树并凭意义。你一味待理解这仿佛实现了争方法,每个方法是啥意思就是足够了。

该,实现类似的时刻,只需要关爱自己应有提供什么方法。不用再行纠结接口需要拆得多缜密才成立。接口是由使用方按需要定义,而休用事先设计。

其三,不用为促成一个接口而import一个保证,目的只有是引用其中的某部interface的概念,这是免吃推荐的。因为多引用一个标的package,就代表又多之耦合。接口由用方按自身需求来定义,使用着随便需关注是否生其它模块定义了类似的接口。

接口赋值

接口(interface)的赋值在Go语言中分为如下2种植情况讨论:

  • 用目标实例赋值给接口
  • 拿接口赋值给任何一个接口

预先讨论将某种类型的目标实例赋值给接口。这要求该目标实例实现了接口要求的持有术。例如,在事先我们来实地作了一个Integer类型,如下:

type Integer int

func (a Integer) Less(b Integer) bool {
    return a < b
}

func (a *Integer) Add(b Integer) {
    *a += b
}

相应地,我们定义接口LessAdder,如下:

type LessAdder interface {
    Less(b Integer) bool
    Add(b Integer)
}

如今发只问题:假设我们定义一个Integer类型的靶子实例,怎么其赋值给LessAdder接口呢?应该据此底的语句(1),还是言语(2)呢?

var a Integer = 1
var b LessAdder = &a     ... (1)
var b LessAdder = a      ... (2)

答案是应当用语句(1)。原因在,Go语言可以依据

func (a Integer) Less(b Integer) bool

这函数自动生成一个初的Less方法:

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

这样,类型
*Integer就既存在Less方法,也是Add方法,满足LessAdder接口。而起另一方面来说,根据

func (a *Integer) Add(b Integer)

本条函数无法自动生成

func (a Integer) Add(b Integer) {
    (&a).Add(b)
}


(&a).Add改变的仅仅是函数参数a,对外表实际而操作的目标并凭影响,这不称用户之预期。故此,Go语言不见面活动吗那蛮成该函数。因此,类型Integer只存在Less方法,缺少Add方法,不满足LessAdder接口,故此上面的说话(2)不克赋值。

以更求证以上的推理,我们不妨重复定义一个Lesser接口,如下:

type Lesser interface {
    Less(b Integer) bool
}

下一场我们定义一个Integer类型的目标实例,将该赋值给Lesser接口:

var a Integer = 1
var b1 Lesser = &a     ... (1)
var b2 Lesser = a      ... (2)

正好使一旦我辈所预期的那样,语句(1)和讲话(2)均可以编译通过。

咱们更来讨论另一样栽情景:将接口赋值给另外一个接口。在Go语言中,只要简单个接口拥有相同的不二法门列表(次序不同不要紧),那么他们不怕平等的,可以并行赋值。例如:

package one

type ReadWriter interface {
    Read(buf []byte) (n int, err error)
    Write(buf []byte) (n int, err error)
}

package two

type IStream interface {
    Write(buf []byte) (n int, err error)
    Read(buf []byte) (n int, err error)
}

这里我们定义了区区个接口,一个深受 one.ReadWriter,一个被
two.IStream。两者都定义了Read、Write方法,只是概念之次相反。one.ReadWriter先定义了Read再定义Write,而two.IStream反之。

于Go语言中,这有限单接口实际上并随便别。因为:

  • 其他实现了one.ReadWriter接口的近乎,均落实了two.IStream。
  • 任何one.ReadWriter接口目标可是赋值给two.IStream,反之亦然。
  • 在任何地方使用one.ReadWriter接口,和应用two.IStream并无别。

以下这些代码C++可编译通过:

var file1 two.IStream = new(File)
var file2 one.ReadWriter = file1
var file3 two.IStream = file2

接口赋并无要求少独接口必须等价格。如果接口A方法列表是接口B方法列表的子集,那么接口B可以赋值给接口A。例如假设我们来Writer接口:

type Writer interface {
    Write(buf []byte) (n int, err error)
}

俺们好用上面的one.ReadWriter、two.IStream接口的实例赋值给Writer接口:

var file1 two.IStream = new(File)
var file4 Writer = file1

不过转头并无建:

var file1 Writer = new(File)
var file5 two.IStream = file1 // 编译不能通过!

这段代码无法编译通过。原因是醒目的:file1并没有Read方法。

接口查询

发出艺术给方Writer接口转换为two.IStream接口么?有。那就算是咱将讨论的接口查询语法。代码如下:

var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
    ...
}

这个if语句的含义是:file1接口指向的靶子实例是否实现了two.IStream接口也?如果实现了,则…
接口查询是否成功,要于运行期才能够确定。它不像接口赋值,编译器只待经过静态类型检查即可判断赋值是否中。

当Windows下开过开之总人口,通常还碰过COM,知道COM也起一个接口查询(QueryInterface)。是的,Go语言的接口查询以及COM的接口查询(QueryInterface)非常类似,都可由此对象(组件)的某接口来查询对象实现之别样接口。当然Go语言的接口查询优雅很多。在Go语言中,对象是不是满足某个接口、通过某接口查询其他接口,这一切都是完全自行就的。

为语言内置接口查询,这是千篇一律项大了不起的事体。在COM中贯彻QueryInterface的长河异常复杂,但QueryInterface是COM体系之向。COM书籍对QueryInterface的介绍,往往由接近下面这样平等段落问话开始,它以Go语言中千篇一律适用:

> 你见面意外为? // IFly
> 不会。
> 你晤面游泳吗? // ISwim
> 会。
> 你见面叫么? // IShout
> 会。
> …

随着问题深刻,你从上马针对目标(组件)一无所知(在Go语言中凡是interface{},在COM中凡IUnknown),到逐渐来了深切之问询。

然若说到底会完全了解对象么?COM说非克,你只能无限逼近,但千古不能够全了解一个组件。Go语言说:你能。

每当Go语言中,你可往接口询问,它对的对象是否是某某项目,例子如下:

var file1 Writer = ...
if file6, ok := file1.(*File); ok {
    ...
}

其一if语句的意义是:file1接口指向的靶子实例是否是 *File
类型呢?如果是的,则…

若得看查询接口所指向的对象是否是某某项目,只是接口查询的一个特例。接口是针对同组项目的公共特性的架空。所以查询接口及查询具体项目的别,好比是下面这片词提问的区分:

> 你是先生为?
> 是。
> 你是某有?
> 是。

先是句提问话查的凡一个群体,是询问接口;而第二词提问已经交了现实的私,是询问具体品种。

在C++/Java/C#
等语言中,也出一对看似的动态查询能力,比如查询一个对象的种类是否是连续自某个项目(基类查询),或者是否实现了某个接口(接口派生查询)。但是她们之动态查询及Go的动态查询好无均等。

> 你是医生也?

于此题材,基类查询看起如是在这么问:“你老爸是医生为?”;接口派生查询则看起如是这样问:“你产生先生执照也?”;在Go语言中,则是优先确定满足哪些的规格才是医,比如技能要求有哪些,然后才是依照标准一一拷问,确认是否满足条件,只要满足了你尽管是医,不体贴而是否来医生执照,或者是小国执照不给天向承认。

项目查询

当Go语言中,你还可更加直接了当地了解接口指向的靶子实例的品种。例如:

var v1 interface{} = ...

switch v := v1.(type) {
    case int: // 现在v的类型是int
    case string: // 现在v的类型是string
    ...
}

便比如现实生活中物种多得勤不到底一如既往,语言中之类型为大抵之再三不清。所以种查询并无常给用。它还多扣起是只上,需要配合接口查询利用。例如:

type Stringer interface {
    String() string
}

func Println(args ...interface{}) {
    for _, arg := range args { 
    switch v := v1.(type) {
    case int: // 现在v的类型是int
    case string: // 现在v的类型是string
    default:
        if v, ok := arg.(Stringer); ok { // 现在v的类型是Stringer
            val := v.String()
            ...
        } else {
            ...
        }
    }
}

Go语言标准库的Println当然比是事例要复杂很多。我们这边摘取其中的要部分开展分析。对于坐类型,Println采用穷举法来,针对每个类别分别转换为字符串进行打印。对于再次相像的情形,首先确定该种是否实现了String()方法,如果实现了则就此String()方法换为字符串进行打印。否则,Println利用反射(reflect)遍历对象的装有成员变量进行打印。

不错,利用反射(reflect)也得展开项目查询,详细而参阅reflect.TypeOf方法有关文档。在后文高阶话题备受我们为会见追究关于“反射(reflect)”的话题。

Any类型

由于Go语言中任何对象实例都满足空接口interface{},故此interface{}看起如是得本着任何对象的Any类型。如下:

var v1 interface{} = 1      // 将int类型赋值给interface{}
var v2 interface{} = "abc"    // 将string类型赋值给interface{}
var v3 interface{} = &v2    // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}

当一个函数可以承受任意的靶子实例时,我们见面以那声明也interface{}。最突出的例证是专业库fmt中PrintXXX系列的函数。例如:

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

前我们曾略分析了Println的实现,也已经显得过interface{}的用法。总结来说,interface{}
类似于COM中之IUnknown,我们刚刚起对其一无所知,但我们可通过接口查询和类查询逐步了解其。

总结

咱俩说,Go
语言的接口(interface)不单独只是只是是接口。在旁语言中,接口就作为组件间的契约存在。从这个范围谈,Go语言接口的重大突破是,其接口是勿侵入式的,把另外语言接口的副作用消除了。

唯独Go语言的接口不仅仅是契约作用。它是Go语言类型系统(type
system)的节骨眼。这展现于:

  • 接口查询:通过接口你得查询接口所指向的目标是否贯彻了另外的接口。
  • 型查询:通过接口你得查询接口所针对的目标的有血有肉品种。
  • Any类型:在Go语言中interface{}可对任意的目标实例。