教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 GOLANG接口适配,组合方式的灵活接口演化

GOLANG接口适配,组合方式的灵活接口演化

发布时间:2022-02-03   编辑:jiaochengji.com
教程集为您提供GOLANG接口适配,组合方式的灵活接口演化等资源,欢迎您收藏本站,我们将为您提供最新的GOLANG接口适配,组合方式的灵活接口演化资源
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"/></svg>

原文:https://gocn.io/article/326

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。

装逼的说来,继承是一种名词化的语言体系,先进行业务抽象然后设计类体系和继承关系。而组合,强制使用接口,因为组合中使用的总是另外一个对象的接口,通过动词的组合,实现目标,比如不管是什么只要有<code>Write([]byte)(int,error)</code>这个动作,就实现了这个接口,其他对象组合这个接口后,对外也看起来就是个<code>io.Writer</code>的接口。

比如,GOALNG1.8支持了<code>writev</code>,一般在面向对象会这么的搞:

<pre class="prettyprint"><code class=" hljs vala"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Socket</span> {</span> <span class="hljs-keyword">int</span> Write(<span class="hljs-keyword">void</span>*, <span class="hljs-keyword">int</span>); <span class="hljs-keyword">int</span> Writev(<span class="hljs-keyword">const</span> iovec*, <span class="hljs-keyword">int</span>); };</code></pre>

对的吧?一个Socket可以写数据,也可以用writev写iovec向量,就是一次性写入多个内存块。

<blockquote>

Note: 有时候内存块是不连续的,比如一个Video帧,发送给不同的客户端时,Header是需要修改的,但是Payload都一样,那么可以针对每个客户端只创建一个header,然后公用payload,但是这时候两个内存指针是不连续的,特别是需要同时写入多个视频帧时,writev就很神奇的避免了内存拷贝<code>writev(header payload)</code>,具体参考下writev的资料哈。

</blockquote>

这样有个问题,并非所有系统都支持Writev的,并非所有Socket都支持Writev的,如果是自己写个代码,当然是可以随便这么搞的,但是作为标准库,GOLANG当然是不能这么做的。GOLANG就加了一个接口(一个新动作)叫做<code>net.buffersWriter</code>,如果实现了这个接口就用writev。先看用法:

<pre class="prettyprint"><code class=" hljs cs"> conn,err := net.Dial(<span class="hljs-string">"tcp"</span>, <span class="hljs-string">"127.0.0.1:1935"</span>) buffers := Buffers{ []<span class="hljs-keyword">byte</span>(<span class="hljs-string">"once upon a time in "</span>), []<span class="hljs-keyword">byte</span>(<span class="hljs-string">"Gopherland ... "</span>), } buffers.WriteTo(conn)</code></pre>

在Buffers的<code>WriteTo</code>方法会判断是否是writev的接口,如果是则用writev写,否则就一个个的写:

<pre class="prettyprint"><code class=" hljs lua">func (v *Buffers) WriteTo(w <span class="hljs-built_in">io</span>.Writer) (n int64, err <span class="hljs-built_in">error</span>) { <span class="hljs-keyword">if</span> wv, ok := w.(buffersWriter); ok { <span class="hljs-keyword">return</span> wv.writeBuffers(v) }</code></pre>

实际上conn是<code>net.TcpConn</code>,里面有个<code>fd *net.netFD</code>,它实现了<code>net.buffersWriter</code>接口,所以最后调用的就是<code>(fd *netFD) writeBuffers(v *Buffers)</code>。

<pre class="prettyprint"><code class=" hljs go"><span class="hljs-keyword">func</span> (c *conn) writeBuffers(v *Buffers) (<span class="hljs-typename">int64</span>, error) { n, err := c.fd.writeBuffers(v) <span class="hljs-keyword">func</span> (fd *netFD) writeBuffers(v *Buffers) (n <span class="hljs-typename">int64</span>, err error) { iovecs = <span class="hljs-built_in">append</span>(iovecs, syscall.Iovec{Base: &chunk<span class="hljs-number">[0</span>]}) wrote, _, e0 := syscall.Syscall(syscall.SYS_WRITEV, <span class="hljs-typename">uintptr</span>(fd.sysfd), <span class="hljs-typename">uintptr</span>(unsafe.Pointer(&iovecs<span class="hljs-number">[0</span>])), <span class="hljs-typename">uintptr</span>(<span class="hljs-built_in">len</span>(iovecs)))</code></pre>

对于其他没有实现这个接口的对象,就每个向量循环的写。

在看一个例子<code>http.Get(url string)</code>,客户端发起一个HTTP请求:

<pre class="prettyprint"><code class=" hljs livecodeserver"><span class="hljs-keyword">http</span>.Get(<span class="hljs-string">"http://localhost:1985/api/v1/versions"</span>)<span class="hljs-comment"> // 实际上调用的是:</span> func (c *Client) Get(url <span class="hljs-keyword">string</span>)<span class="hljs-comment"> // 然后调用:</span> (c *Client) Do(req *Request)</code></pre>

在GOLANG1.7中引入了context的概念,用来支持cancel,怎么用的:

<pre class="prettyprint"><code class=" hljs r">ctx,cancel := context.WithCancel(context.Background()) select { case <- ctx.Done(): // Cancelled. case <- time.After(<span class="hljs-keyword">...</span>): // Timeout case <- other events: // Other events. }</code></pre>

如何支持取消的HTTP请求呢?给<code>http.Get</code>加个ctx参数?例如<code>http.Get(ctx, url)</code>这样?那改动得多大啊,而且还不能兼容之前的API,泪奔~看看GOLANG的解决:

<pre class="prettyprint"><code class=" hljs avrasm">ctx,cancel := context<span class="hljs-preprocessor">.WithCancel</span>(context<span class="hljs-preprocessor">.Background</span>()) go func(){ req,err := http<span class="hljs-preprocessor">.NewRequest</span>(<span class="hljs-string">"http://..."</span>) res,err := http<span class="hljs-preprocessor">.DefaultClient</span><span class="hljs-preprocessor">.Do</span>(req<span class="hljs-preprocessor">.WithContext</span>(ctx)) defer res<span class="hljs-preprocessor">.Body</span><span class="hljs-preprocessor">.Close</span>() // 读取res响应结果。 }() select { case <- ctx<span class="hljs-preprocessor">.Done</span>(): case <- time<span class="hljs-preprocessor">.After</span>(<span class="hljs-number">3</span> * time<span class="hljs-preprocessor">.Second</span>): cancel() // Timeout to cancel all requests. }</code></pre>

使用组合,通过<code>req.WithContext</code>再返回一个<code>*http.Request</code>,实现同样的目的。

到此这篇关于“GOLANG接口适配,组合方式的灵活接口演化”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
GOLANG接口适配,组合方式的灵活接口演化
19小接口的妙用
go语言中的继承和接口使用(八)
go语言学习笔记(十三)——接口类型
接口隔离原则是什么?
适配器模式是什么?
Go语言基础之接口(面向对象编程下)
golang 面试题(四)go的组合继承
关于golang面向接口
golang接口初解析

[关闭]
~ ~