程序设计一个很重要的优化点是减少重复代码。一件完成了的事情没人愿意做多次,一堆代码没人愿意重复写好几遍,哪怕是复制粘贴。
但当接触的 Golang 代码多了,渐渐的需要用 Golang 写一些有用的项目,发现有时候极难复用已有的代码。思来想去,一个号称具备生产力的语言怎能容忍弱鸡的复用代码的能力。
下面举个例子:
这个片断企图改写 http 包 Client 结构的方法以达到传递 context 的效果。因发起 HTTP 请求的各大方法都通过调用 Do 方法来执行请求,常规思维下,只需新声明一个结构继承 Client 然后重写 Do 方法就行了。
于是就可以通过下面代码中的方式传递自定义的 context :
曾经的 Java 经验,让我会觉得这是很自然的一种写法。
在刚接触 Golang 语言时,通过各种途径发现的事实是它不是面向对象的语言,它没有继承,也没有方法重写。多年代码设计的经验告诉我,当一种模式缺失时,它一定会以另一种形式出现,从逻辑上来看它们应该等价。
后来得知了一个概念:Embedding。即,可以在 Golang 的 struct 或 interface 中嵌入其他的 struct 或 interface。表现出来的效果就像是继承。于是就有了上面例子的写法。
当时写这段代码的时候非常希望能按预期工作。直接复用标准库中的代码,只用改写应该改写的代码,看起来也是优雅的,没有堆砌感。
可遗憾的是并不能按预想的方式运行,context 并不会传递下去,也就是 req 的 ctx 值并不会被替换掉。Golang 没有继承。于是不得不妥协采用其他常规的方式来实现。如下:
因重复的部分有点多,所以这个版本只实现了 GET、 HEAD 和 POST 这三个方法,其他的直接调用 Do。为了传递 context 不得不重复写所有的方法,写起来真的是特别累。
通过上面的改写,在使用的时候就是下面这个样子:
Get 的第一个参数传递 context ,应该是很熟悉的写法。
Golang 另一种避免重复写大量代码的方式是用工具生成代码。偶尔也会用到这个方法。但这其实是另一个维度的概念了。