这篇文章将形容代码中常常应用的抢占式接口模式,以及为什么我认为在 Go 中遵循这种模式通常是不正确的。
什么是抢占式接口
接口是一种形容行为的形式,存在于大多数类型语言中。抢占式接口是指开发人员在理论须要呈现之前对接口进行编码。一个示例可能如下所示。
type Auth interface {GetUser() (User, error)
}
type authImpl struct {// ...}
func NewAuth() Auth {return &authImpl}
抢占式接口何时有用
抢占接口通常用于在 Java 中, 并且大获胜利,这是大部分程序员的想法。置信,很多 Go 开发者也是这么认为的。这种用法次要区别在于 Java 具备显式接口,而 Go 是隐式接口。让咱们看一些示例 Java 代码,这些代码显示了如果不应用 Java 中的抢占式接口可能会呈现的艰难。
// auth.java
public class Auth {public boolean canAction() {// ...}
}
// logic.java
public 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.java
public interface Auth {public boolean canAction()
}
// authimpl.java
class AuthImpl implements Auth {
}
// logic.java
public 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,而不会毁坏向后兼容性。接口是一种形象,形象有时很有用。然而,不必要的形象会造成不必要的复杂化。在须要之前不要使代码过于简单。