教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Go语言学习(十五)面向对象编程-接口

Go语言学习(十五)面向对象编程-接口

发布时间:2022-02-25   编辑:jiaochengji.com
教程集为您提供Go语言学习(十五)面向对象编程-接口等资源,欢迎您收藏本站,我们将为您提供最新的Go语言学习(十五)面向对象编程-接口资源
<h2 id="1其他语言的接口">1.其他语言的接口</h2>

Go语言的接口并不是其他语言(C 、Java、C#等)中所提供的接口概念。
在Go语言出现之前,接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你
必须声明你的确实现了该接口。为了实现一个接口,你需要从该接口继承:

<pre class="prettyprint"><code class="language-java hljs ">interface IFoo { <span class="hljs-keyword">void</span> Bar(); } class Foo implements IFoo { <span class="hljs-comment">// Java语法</span> <span class="hljs-comment">// ...</span> }</code></pre>

即使另外有一个接口IFoo2实现了与IFoo有完全一样的接口方法甚至名字也叫IFoo只不过位
于不同的名字空间(包名)下,编译器也会认为上面的类Foo只实现了IFoo而没有实现IFoo2接口。
这类接口我们称为侵入式接口。“侵入式”的主要表现在于实现类需要明确声明自己实现了
某个接口。这种强制性的接口继承是面向对象编程思想发展过程中一个遭受相当多置疑的特性。

<h2 id="2非侵入式接口">2.非侵入式接口</h2>

在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,
例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> File <span class="hljs-keyword">struct</span> { <span class="hljs-comment">// ...</span> } <span class="hljs-keyword">func</span> (f *File) Read(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) <span class="hljs-keyword">func</span> (f *File) Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) <span class="hljs-keyword">func</span> (f *File) Seek(off <span class="hljs-typename">int64</span>, whence <span class="hljs-typename">int</span>) (pos <span class="hljs-typename">int64</span>, err error) <span class="hljs-keyword">func</span> (f *File) Close() error</code></pre>

这里我们定义了一个File类并实现有Read()、Write()、Seek()、Close()等方法。设
想我们有如下接口:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> IFile <span class="hljs-keyword">interface</span> { Read(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) Seek(off <span class="hljs-typename">int64</span>, whence <span class="hljs-typename">int</span>) (pos <span class="hljs-typename">int64</span>, err error) Close() error } <span class="hljs-keyword">type</span> IReader <span class="hljs-keyword">interface</span> { Read(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) } <span class="hljs-keyword">type</span> IWriter <span class="hljs-keyword">interface</span> { Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) } <span class="hljs-keyword">type</span> ICloser <span class="hljs-keyword">interface</span> { Close() error }</code></pre>

尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,只要File类
有包含这些接口的所有方法,就可以如下赋值使用.

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> file1 IFile = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file2 IReader = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file3 IWriter = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file4 ICloser = <span class="hljs-built_in">new</span>(File)</code></pre>

是不是很强大,最大的特点就是不需要明确(显式)声明自己实现了某个接口.

Go语言的非侵入式接口,看似只是做了很小的文法调整,实则影响深远。
其一,Go语言的标准库,再也不需要绘制类库的继承树图.

在Go中,类的继承树并无意义,你只需要知道这个类实现了哪些方法,每个方法是啥含义
就足够了。
其二,实现类的时候,只需要关心自己应该提供哪些方法,不用再纠结接口需要拆得多细才
合理。接口由使用方按需定义,而不用事前规划。
其三,不用为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦
合。接口由使用方按自身需求来定义,使用方无需关心是否有其他模块定义过类似的接口。

<h2 id="3接口的赋值">3.接口的赋值</h2>

接口赋值在Go语言中分为如下两种情况:
1)将对象实例赋值给接口;
2)将一个接口赋值给另一个接口。

先讨论将某种类型的对象实例赋值给接口,这要求该对象实例实现了接口要求的所有方法,
例如之前我们作过一个 Integer 类型,如下:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> Integer <span class="hljs-typename">int</span> <span class="hljs-keyword">func</span> (a Integer) Less(b Integer) <span class="hljs-typename">bool</span> { <span class="hljs-keyword">return</span> a < b } <span class="hljs-keyword">func</span> (a *Integer) Add(b Integer) { *a = b }</code></pre>

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

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> LessAdder <span class="hljs-keyword">interface</span> { Less(b Integer) <span class="hljs-typename">bool</span> Add(b Integer) }</code></pre>

现在有个问题:假设我们定义一个Integer类型的对象实例,怎么将其赋值给LessAdder
接口呢?应该用下面的语句(1),还是语句(2)呢?

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> a Integer =<span class="hljs-number"> 1</span> <span class="hljs-keyword">var</span> b LessAdder = &a ...<span class="hljs-number">(1</span>) <span class="hljs-keyword">var</span> b LessAdder = a ... <span class="hljs-number">(2</span>)</code></pre>

答案是应该用语句(1)。原因在于,Go语言可以根据下面的函数:
<code>func (a Integer) Less(b Integer) bool</code>
自动生成一个新的Less()方法:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">func</span> (a *Integer) Less(b Integer) <span class="hljs-typename">bool</span> { <span class="hljs-keyword">return</span> (*a).Less(b) }</code></pre>

这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口的所有方法。

为了进一步证明以上的推理,我们不妨再定义一个Lesser接口,如下:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> Lesser <span class="hljs-keyword">interface</span> { Less(b Integer) <span class="hljs-typename">bool</span> }</code></pre>

然后定义一个Integer类型的对象实例,将其赋值给Lesser接口:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> a Integer =<span class="hljs-number"> 1</span> <span class="hljs-keyword">var</span> b1 Lesser = &a ...<span class="hljs-comment">//(1)</span> <span class="hljs-keyword">var</span> b2 Lesser = a ... <span class="hljs-comment">//(2)</span></code></pre>

正如我们所料的那样,语句(1)和语句(2)均可以编译通过。

我们再来讨论另一种情形:
将一个接口赋值给另一个接口。在Go语言中,只要两个接口拥
有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。
例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">package</span> one <span class="hljs-keyword">type</span> ReadWriter <span class="hljs-keyword">interface</span> { Read(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) }</code></pre>

第二个接口位于另一个包中:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">package</span> two <span class="hljs-keyword">type</span> IStream <span class="hljs-keyword">interface</span> { Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) Read(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) }</code></pre>

这里我们定义了两个接口,一个叫 one.ReadWriter,一个叫two.Istream,两者都定义
了Read()、Write()方法,只是定义次序相反,one.ReadWriter先定义了Read()再定义了
Write(),而two.IStream反之。
在Go语言中,这两个接口实际上并无区别,因为:
1)任何实现了one.ReadWriter接口的类,均实现了two.IStream;
2)任何实现了one.ReadWriter接口的类,均实现了two.IStream;
3)在任何地方使用one.ReadWriter接口与使用two.IStream并无差异。

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

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> file1 two.IStream = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file2 one.ReadWriter = file1 <span class="hljs-keyword">var</span> file3 two.IStream = file2</code></pre>

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

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> Writer <span class="hljs-keyword">interface</span> { Write(buf []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error) }</code></pre>

就可以将上面的one.ReadWriter和two.IStream接口的实例赋值给Writer接口:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> file1 two.IStream = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file4 Writer = file1 <span class="hljs-comment">//赋值给子集接口</span></code></pre>

但是反过来并不成立:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> file1 Writer = <span class="hljs-built_in">new</span>(File) <span class="hljs-keyword">var</span> file5 two.IStream = file1 <span class="hljs-comment">// 编译不能通过</span></code></pre>

这段代码无法编译通过,原因是显然的:file1并没有Read()方法。

<h2 id="4接口查询">4.接口查询</h2>

有办法判断Writer接口是否可以转换为two.IStream接口呢?
有,那就是我们即将讨论的接口查询语法,代码如下:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> file1 Writer = ... <span class="hljs-keyword">if</span> file5, ok := file1.(two.IStream); ok { ... }</code></pre>

这个if语句检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执
行特定的代码。
类似的java语法中instanceof,比如查询一个对象的类型是否继承自某个类型(基类查询),
或者是否实现了某个接口(接口派生查询),但是它们的动态查询与Go的动态查询很不一样。

<h2 id="5类型查询">5.类型查询</h2>

在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型,例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> v1 <span class="hljs-keyword">interface</span>{} = ... <span class="hljs-keyword">switch</span> v := v1.(<span class="hljs-keyword">type</span>) { <span class="hljs-keyword">case</span> <span class="hljs-typename">int</span>: <span class="hljs-comment">// 现在v的类型是int</span> <span class="hljs-keyword">case</span> <span class="hljs-typename">string</span>: <span class="hljs-comment">// 现在v的类型是string</span> ... }</code></pre>

就像现实生活中物种多得数不清一样,语言中的类型也多得数不清,所以类型查询并不经常
使用。它更多是个补充,需要配合接口查询使用,例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">package</span> main <span class="hljs-keyword">import</span> ( <span class="hljs-string">"fmt"</span> ) <span class="hljs-keyword">type</span> Stringer <span class="hljs-keyword">interface</span> { <span class="hljs-comment">//定义接口</span> String() <span class="hljs-typename">string</span> } <span class="hljs-keyword">type</span> MyStringer <span class="hljs-keyword">struct</span>{ <span class="hljs-comment">//定义结构体</span> } <span class="hljs-keyword">func</span> (*MyStringer) String()<span class="hljs-typename">string</span>{ <span class="hljs-comment">//实现接口的方法</span> <span class="hljs-keyword">return</span> <span class="hljs-string">"this is a method implement from Stringer"</span> } <span class="hljs-keyword">func</span> main(){ Println<span class="hljs-number">(123</span>) Println(<span class="hljs-string">"mChenys"</span>) <span class="hljs-keyword">var</span> myStringer Stringer = <span class="hljs-built_in">new</span>(MyStringer) Println(myStringer) } <span class="hljs-keyword">func</span> Println(args ...<span class="hljs-keyword">interface</span>{}) {<span class="hljs-comment">//不定参数</span> <span class="hljs-keyword">for</span> _, arg := <span class="hljs-keyword">range</span> args { <span class="hljs-keyword">switch</span> v := arg.(<span class="hljs-keyword">type</span>) {<span class="hljs-comment">//判断类型</span> <span class="hljs-keyword">case</span> <span class="hljs-typename">int</span>: fmt.Println(<span class="hljs-string">"现在"</span>,v,<span class="hljs-string">"的类型是int"</span>) <span class="hljs-keyword">case</span> <span class="hljs-typename">string</span>: fmt.Println(<span class="hljs-string">"现在"</span>,v,<span class="hljs-string">"的类型是string"</span>) <span class="hljs-keyword">default</span>: <span class="hljs-keyword">if</span> v, ok := arg.(Stringer); ok { <span class="hljs-comment">// 现在v的类型是Stringer</span> val := v.String() fmt.Println(val) } <span class="hljs-keyword">else</span> { fmt.Println(<span class="hljs-string">"其它类型"</span>) } } } }</code></pre> <blockquote>

//输出结果:
现在 123 的类型是int
现在 mChenys 的类型是string
this is a method implement from Stringer

</blockquote> <h2 id="6接口组合嵌套">6.接口组合(嵌套)</h2>

像之前介绍的类型(结构体)组合一样,Go语言同样支持接口组合,例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-comment">// ReadWriter接口将基本的Read和Write接口组合起来</span> <span class="hljs-keyword">type</span> ReadWriter <span class="hljs-keyword">interface</span> { Reader Writer }</code></pre>

这个接口组合了Reader和Writer两个接口,它完全等同于如下写法:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">type</span> ReadWriter <span class="hljs-keyword">interface</span> { Read(p []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error)<span class="hljs-comment">//Reader的方法</span> Write(p []<span class="hljs-typename">byte</span>) (n <span class="hljs-typename">int</span>, err error)<span class="hljs-comment">//Writer的方法</span> }</code></pre>

因为这两种写法的表意完全相同:ReadWriter接口既能做Reader接口的所有事情,又能做
Writer接口的所有事情.在Go语言包中,还有众多类似的组合接口,比如ReadWriteCloser、
ReadWriteSeeker、ReadSeeker和WriteCloser等。

可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何
成员变量。

<h2 id="7any类型">7.Any类型</h2>

由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可
以指向任何对象的Any类型,如下:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">var</span> v1 <span class="hljs-keyword">interface</span>{} =<span class="hljs-number"> 1</span> <span class="hljs-comment">//将int类型赋值给interface{}</span> <span class="hljs-keyword">var</span> v2 <span class="hljs-keyword">interface</span>{} = <span class="hljs-string">"abc"</span> <span class="hljs-comment">//将string类型赋值给interface{}</span> <span class="hljs-keyword">var</span> v3 <span class="hljs-keyword">interface</span>{} = &v2 <span class="hljs-comment">//将*interface{}类型赋值给interface{}</span> <span class="hljs-keyword">var</span> v4 <span class="hljs-keyword">interface</span>{} = <span class="hljs-keyword">struct</span>{ X <span class="hljs-typename">int</span> }<span class="hljs-number">{1</span>} <span class="hljs-keyword">var</span> v5 <span class="hljs-keyword">interface</span>{} = &<span class="hljs-keyword">struct</span>{ X <span class="hljs-typename">int</span> }<span class="hljs-number">{1</span>}</code></pre>

当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标
准库fmt中PrintXXX系列的函数,例如:

<pre class="prettyprint"><code class="language-go hljs "><span class="hljs-keyword">func</span> Printf(fmt <span class="hljs-typename">string</span>, args ...<span class="hljs-keyword">interface</span>{}) <span class="hljs-keyword">func</span> Println(args ...<span class="hljs-keyword">interface</span>{}) ...</code></pre>
<script type="text/javascript"></script>
到此这篇关于“Go语言学习(十五)面向对象编程-接口”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Go语言发展历史、核心、特性及学习路线
想系统学习GO语言(Golang
Go 语言到底适合干什么?
【Go语言入门系列】(八)Go语言是不是面向对象语言?
Go语言学习3----Go语言特色
php入门教程(索引)
Golang学习笔记(五):Go语言与C语言的区别
兄弟连golang神技(1)-关于 Go 语言的介绍
Golang学习笔记(十二):接口的声明与使用
Go语言学习(十五)面向对象编程-接口

[关闭]
~ ~