这篇文章将描述代码中常用的抢占式接口模式,以及为何我认为在Go中遵循这种模式一般是不正确的。java
接口是一种描述行为的方式,存在于大多数类型语言中。抢占式接口是指开发人员在实际须要出现以前对接口进行编码。一个示例可能以下所示。程序员
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接口。在具备显式接口的语言中,之后你会感谢过去的本身使用了抢占式接口。code
让咱们在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的接口定义都是很小,但很强大。在标准库中,大多数接口定义都是单一方法。这容许最大的重用,由于实现接口很容易。当程序员对像上面的Auth这样的抢占式接口进行编码时,接口的方法数量每每会激增,这使得接口(可交换实现)的所有意义更难以实现。io
Go的一个很好的经验法则是-接受接口,返回结构体。接受接口为您的API提供了最大的灵活性,返回结构体容许调用者快速导航到正确的函数。class
即便你的Go代码接受结构体并返回结构体以启动,隐式接口也容许您稍后扩展你的API,而不会破坏向后兼容性。接口是一种抽象,抽象有时颇有用。然而,没必要要的抽象会形成没必要要的复杂化。在须要以前不要使代码过于复杂。