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

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

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

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

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

class Socket {
int Write(void*, int);
int Writev(const iovec*, int);
};

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

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

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

    conn,err := net.Dial("tcp", "127.0.0.1:1935")

    buffers := Buffers{
        []byte("once upon a time in "),
        []byte("Gopherland ... "),
    }

    buffers.WriteTo(conn)

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

func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
    if wv, ok := w.(buffersWriter); ok {
        return wv.writeBuffers(v)
    }

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

func (c *conn) writeBuffers(v *Buffers) (int64, error) {
    n, err := c.fd.writeBuffers(v)

func (fd *netFD) writeBuffers(v *Buffers) (n int64, err error) {
        iovecs = append(iovecs, syscall.Iovec{Base: &chunk[0]})
        wrote, _, e0 := syscall.Syscall(syscall.SYS_WRITEV,
            uintptr(fd.sysfd),
            uintptr(unsafe.Pointer(&iovecs[0])),
            uintptr(len(iovecs)))

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

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

http.Get("http://localhost:1985/api/v1/versions")
// 实际上调用的是:
func (c *Client) Get(url string)
// 然后调用:
(c *Client) Do(req *Request)

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

ctx,cancel := context.WithCancel(context.Background())

select {
case <- ctx.Done():
    // Cancelled.
case <- time.After(...):
    // Timeout
case <- other events:
    // Other events.
}

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

ctx,cancel := context.WithCancel(context.Background())
go func(){
    req,err := http.NewRequest("http://...")
    res,err := http.DefaultClient.Do(req.WithContext(ctx))
    defer res.Body.Close()
    // 读取res响应结果。
}()

select {
case <- ctx.Done():
case <- time.After(3 * time.Second):
    cancel() // Timeout to cancel all requests.
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

winlinvip

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值