这篇文章将形容代码中常常应用的抢占式接口模式,以及为什么我认为在Go中遵循这种模式通常是不正确的。

什么是抢占式接口

接口是一种形容行为的形式,存在于大多数类型语言中。抢占式接口是指开发人员在理论须要呈现之前对接口进行编码。一个示例可能如下所示。

type Auth interface {  GetUser() (User, error)}type authImpl struct {  // ...}func NewAuth() Auth {  return &authImpl}

抢占式接口何时有用

抢占接口通常用于在Java中,并且大获胜利,这是大部分程序员的想法。置信,很多Go开发者也是这么认为的。这种用法次要区别在于Java具备显式接口,而Go是隐式接口。让咱们看一些示例Java代码,这些代码显示了如果不应用Java中的抢占式接口可能会呈现的艰难。

// auth.javapublic class Auth {  public boolean canAction() {    // ...  }}// logic.javapublic class Logic {  public void takeAction(Auth a) {    // ...  }}

当初假如您要更改Logic的takeAction办法中的参数Auth类型的对象,只有它具备canAction()办法即可。可怜的是,你不能。Auth没有在其中实现带有canAction()的接口。你当初必须批改Auth为其提供一个接口,而后您能够在takeAction中承受该接口,或者将Auth包装在一个除了实现的办法之外什么都不做的类中。即便logic.java定义了一个Auth接口以在takeAction()中承受,也可能很难让Auth实现该接口。您可能无权批改Auth,或者Auth可能位于第三方库中。兴许Auth的作者不批准你的批改。兴许在代码库中与共事共享Auth,当初须要在批改之前达成共识。这是心愿的Java代码。

// auth.javapublic interface Auth {  public boolean canAction()}// authimpl.javaclass AuthImpl implements Auth {}// logic.javapublic class Logic {  public void takeAction(Auth a) {    // ...  }}

如果Auth的作者最后编码并返回一个接口,那么你在尝试扩大takeAction时,永远不会遇到问题。它天然实用于任何Auth接口。在具备显式接口的语言中,当前你会感激过来的本人应用了抢占式接口。

为什么这在Go中不是问题

让咱们在Go中设置雷同的状况。

// auth.go type Auth struct { // ... }// logic.go func TakeAction(a *Auth) {   // ... }

如果logic想要使TakeAction通用,则logic所有者能够单方面执行此操作,而不会打搅其他人。

// logic.go type LogicAuth interface {   CanAction() bool }func TakeAction(a LogicAuth) {   // ... }

请留神 auth.go 不须要更改。这是使抢占式接口不再须要的关键所在。

Go中抢占式接口的意外副作用

Go的接口定义都是很小,但很弱小。在规范库中,大多数接口定义都是繁多办法。这容许最大的重用,因为实现接口很容易。当程序员对像下面的Auth这样的抢占式接口进行编码时,接口的办法数量往往会激增,这使得接口(可替换实现)的全副意义更难以实现。

Go中接口的最佳用法

Go的一个很好的教训法令是-承受接口,返回构造体。承受接口为您的API提供了最大的灵活性,返回构造体容许调用者疾速导航到正确的函数。

即便你的Go代码承受构造体并返回构造体以启动,隐式接口也容许您稍后扩大你的API,而不会毁坏向后兼容性。接口是一种形象,形象有时很有用。然而,不必要的形象会造成不必要的复杂化。在须要之前不要使代码过于简单。