No.1 解释什么是地道的Go代码, 为什么你要遵循它。

ACCEPT INTERFACES RETURN CONCRETE TYPES(接收接口返回的具体类型)

OUTLINES WHY YOU SHOULD WRITE YOUR LIBRARIES AND PACKAGES TO ACCEPT INTERFACES AND RETURN CONCRETE TYPES
要点:为什么你应该编写库和包用以接收接口和返回具体的类型。

Note this post was edited after taking on board feedback from reddit discussion
注意这个文章经过参与讨论的反馈后被编辑

I have heard mentioned several times this idea of accepting interfaces and returning concrete types. Here I will try to outline why I think this is good practice. It is important to note that this is intended as a principal to keep in mind when writing Go code, rather than an absolute rule.
我听到过很多次这种接收接口和返回具体类型的想法。在这里,我将尝试说明为什么本人认为这是一个优秀的写法。重要的注释:当在编写Go代码的时候,要记住这一点,然而这不是绝对的规则。

When writing libraries and packages our goal is for them to be consumed by someone. Either by our own code, but also, hopefully, by others too. We want to make this as simple and frictionless as possible. Accepting interfaces and returning structs can be a powerful way to achieve this. It allows the consumers of our packages to reduce the coupling between their code and yours. It helps clearly define the contract between API and the consumer, it makes it easier when consumers of your code are writing tests for the code that depends on your package. Lets look at some examples to help illustrate.
当我们在编写库和包的时候我们的目标是让他们被某人使用。或者是我们自己的代码,但也希望其他人也能怎么做。我们想让他尽可能的简单和无摩擦。接收接口和返回结构体可以实现这一点的强有力的方法。他允许我们的包的使用者减少他们的代码和你的代码之间的耦合。它有助于清晰的定义API和使用者之间的约定,当您的代码的使用者为依赖于您的包的代码编写测试时,它会更容易。让我们看一些例子来帮助说明。

Accepting Interfaces(接收接口)

Accepting a concrete type can limit the uses or our API and also cause difficulty for consumers of our code when it comes to testing. For example, if the public API of our library or package were to accept the concrete type os.File instead of the io.Writer interface, it would force consumers to use the same types in order to use our API. Now if we were instead to accept an interface, it would ensure that the requirements of our API are met, while not forcing a concrete type on the consumer.
接收具体类型可以限制用途或者API,并且在涉及到测试时也会对我们的代码的使用者造成困难。例如,如果我们的库和包的公有API是接收具体类型
os.Fileer而不是io.Writer接口,他将强制使用者使用相同的类型以使用我们的API。现在,如果我们改为接收一个接口,那么他将确保满足我们API的要求,而不会强制使用者使用具体类型。

Below is a contrived, simple example, but it is something you can often hit in real world code.
下面是一个精心设计的简单实例,但他是你经常可以在现实世界中使用的代码。

Using a concrete type(使用具体类型)

package myapi

import "os"

type MyWriter struct {}

func (mw *MyWriter) UpdateSomething(f *os.File) error {
    //code using the file to write ...
    return nil
}

func New() *MyWriter {
    return &MyWriterApi{}
}

So we mentioned how this pattern effect the consumers of our API. Lets look at how the above API would be consumed and tested:
所以我们提到这种模式如何影响我们API的使用者。 让我们看看上面的API将如何被使用和测试:

 package myconsumer 

  import (
      "github.com/someone/myapi"
  )

  func UseMyApi(doer *myapi.MyWriter, f *os.File)error{
      //do awesome business logic
      return doer.UpdateSomething(f)
  }

In our application code, at first, this seems ok as long as we only need to use files with this API. The difficulty shows itself best when we implement a test.
在我们的应用程序代码中,首先,只要我们只需要使用带有该API的文件就可以了。当我们执行一个测试的时候,这个难点表现的最好。

  package myconsumer_test 

  import (
      "github.com/someone/myconsumer"
      "os"
  )

  type mockDoer struct{}
  func (md mockDoer)UpdateSomething(f *os.File)error{
      return nil
  }

  func TestUseMyApi(t *testing.T){

      //we now need to get a concrete implementation of  *os.File somehow to use with our test.
      f,err := os.Open("/some/path/to/fixture/file.txt")
      if err != nil{
          t.Fatalf("failed to open file for test %s",err.Error())
      }
      defer f.Close()
      if err := myconsumer.UseMyApi(mockDoer{},f); err != nil{
          t.Errorf("did not expect error calling UseMyApi but got %s ", err.Error())
      }
       
  }

As the API implementation can only accept a concret os.File we are now forced to use a real file in our test. So how dow we solve this problem and make our API better for its consumers?
由于API的实现只能接收一个具体
os.File(系统文件),我们现在不得不在测试中使用一个真实的文件。因此,我们如何解决这个问题,让我们API更好为使用者服务?

Accepting an interface(接收一个接口)

Back to API code:(回到API代码)

package myapi

import "io"


type MyWriter struct {}

#Swithing from an io.File to an io.Writer makes things far easer for the consumer.
func (mw *myWriter) UpdateSomething(w io.Writer) error {
    //code using the writer to write ...
    return nil
}

func New() *MyWriter {
    return &MyWriterApi{}
}

Now our implementation takes anything that implements the builtin io.Writer interface. Although we are using a builtin interface here, in code, within specific business domains, this could well be a custom interface expressing the concerns of your domain. So how does this change impact our test code?
现在,我们实现需要实现内置的io.Writer接口的所有东西。虽然我们在这里使用一个内置接口,在代码中,在特定的业务领域,这可能是表达你关注的领域的自定义接口。因此这个变化如何影响我们的测试代码呢?

  package myconsumer_test 

  import (
      "github.com/someone/myconsumer"
      "io"
      "bytes"
  )

  type mockDoer struct{}
  func (md mockDoer)UpdateSomething(w io.Writer)error{
      return nil
  }

  func TestUseMyApi(t *testing.T){

      //we no longer need an actual file, all we need is something that 
      //implements the write method from io.Writer. bytes.Buffer is one such type.
      var b bytes.Buffer
    
      if err := myconsumer.UseMyApi(mockDoer{},&b); err != nil{
          t.Errorf("did not expect error calling UseMyApi but got %s ", err.Error())
      }
       
  }

Another advantage here is that if our UseMyApi function, also writes using the writer, we can assert based on the contents of the bytes.Buffer.
这里另一个优势是,如果我们的UseMyApi函数,也可以使用writer来写,我们可以基于bytes.Buffer的内容来断言。

Reducing our footprint and coupling(减少我们的封装和耦合)

Our libraries may use other libraries for some of its functionality. In our public API we should avoid exposing third party types to the consumers of our API. If our public API exposes a 3rd party type, then our consumers will also need to import and use that third party type. This couples there code to a dependency of your code and means they need to know too much about how the innards of your API works. It is a leaky abstraction. To avoid this we should either define our own types that internally can be translated to the required type or define and accept an interface.
我们的库可以使用其他的库来实现一些功能。在我们共有的API中,我们应该尽量避免第三方类型暴露给API的使用者。如果我们的共有API暴露了第三方类型,那么我们的使用者也需要导入和使用第三方类型,这将代码与代码的依赖关联起来,意味着他们需要了解API内容的工作原理。这是一个漏洞的抽象层,为了避免这种情况,我们应该定义我们自己的类型,内部可以将其转换为所需类型或定义并接受一个接口。

Returning Concrete Types(返回具体类型)

So what are the advantages of returning concrete types? If we want to accept interfaces, why would we not also return interfaces?
所以返回具体类型的优点是什么呢?如果我们想要接收接口,为什么我们不返回接口撒?

Navigating to the implementation code of a returned type, is something that you will often do when using a library even if it is only to get a better understanding of how something works. Returning an interface makes this far more difficult for the consumer as they will first navigate to an interface definition and then need to spend additional time trying to find the implementation code. Consumers of our code, my only be interested in a small subset of the functionality, while returning an interface doesn’t stop them from defining a new interface, nor does returning a concrete type. So given the uneeded indirection a returned interface will cause, it makes more sense and reduces friction to return a concrete type.
导航到返回类型的代码实现,这是你在使用库时经常做的事情,即使它仅仅是为了更好地了解某些工作原理。返回一个接口让使用者变得更加困难,因为它们将首先导航到接口定义,然后需要花费额外的时间尝试查找实现代码。我们代码的使用者,只对一小部分功能感兴趣,而返回接口并不能阻止他们定义新接口,也不会返回具体的类型。因此,考虑到返回的接口将会导致的无方向的间接影响,它会更有意义,减少摩擦以返回一个具体的类型。

原文地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容