這篇文章將描述代碼中常用的搶佔式接口模式,以及爲何我認爲在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,而不會破壞向後兼容性。接口是一種抽象,抽象有時頗有用。然而,沒必要要的抽象會形成沒必要要的複雜化。在須要以前不要使代碼過於複雜。