In Java you have to write the interface first:
// first
interface UserStore { ... }
// then
class PsqlUserStore implements UserStore { ... }
In Go you can write either the implementation, or the interface first.
package psql
type UserStore { ... }
func (us *UserStore) Find(id int) (*app.User, error) { ... }
func (us *UserStore) Create(user *User) error { ... }
func (us *UserStore) Delete(user *User) error { ... }
If you opt to write the implementation first (as is often suggested), then writing the interface is just a matter of extracting methods from a type and adding them to an interface.
package app
type UserStore interface {
Find(id int) (*User, error)
Create(*User) error
Delete(*User) error
}
psql.UserStore
will “magically” implement the app.UserStore
interface because Go uses duck typing (though this is technically not true, it is for our purposes the same as duck typing).
var us app.UserStore = &psql.UserStore{}
Most developers learning Go figure this out pretty quickly and can appreciate it almost immediately. Rather than dealing with arbitrary sets of methods in interfaces based on what some engineer imagined we may one day need we instead had interfaces based on exactly what was needed and nothing more.
Unfortunately the conversation often stops at this point and doesn’t explore the other side of the equation - the fact that duck typing also allows us to write much clearer requirements and dependencies into our code.
What do I mean by this?
Again, let’s go back to Java-land. Imagine you wanted to write some code that only needed to look up a user. You are going to accept an interface so the actual implementation can be changed down the road and to make testing easier, so the final code might look something like this:
void doThing(UserStore userStore) {
// ...
user = userStore.Find(id)
// ...
}
The important bit to look at here is that we have made no clear declarations that the doThing
function only uses the Find
method on the userStore
argument. In fact, there is nothing stopping a future developer from coming along and using any other methods offered by the userStore
variable so long as those methods are defined in the UserStore
interface. This all seems logical enough, but how could we truly be explicit and state, “I only want and only use the Find
method” in our code?
The only way I am aware of to actually enforce this in Java would be to declare an entirely new interface, and to then make sure all of our UserStore
implementations also implement this new interface. Below is a stubbed out example of this.
interface UserStore { ... } // has all the UserStore methods - Find, Create, etc
interface UserFinder { ... } // only has the Find method
// PsqlUserStore has to explicitly implement both interfaces now
class PsqlUserStore implements UserStore, UserFinder { ... }
After making these changes we could update our doThing
function to accept a UserFinder
argument.
void doThing(UserFinder userFinder) {
// ...
user = userFinder.Find(id)
// ...
}
Now I know what you are thinking; this is a lot of work for what feels like very little benefit. And the truth is you are right. You will pretty much never see code like this in a Java project because it is rarely worth the effort. Instead, developers will just accept the UserStore
interface and perhaps document that they only need the Find
method.
Go, on the other hand, makes this much easier because it uses duck typing. To see this in action, let’s take our doThing
function and rewrite it in Go as a function that accepts an interface that only has the Find
method.
func doThing(uf interface {
Find(int) (*User, error)
}) {
// ...
user = uf.Find(id)
// ...
}
Before moving on, a quick recap of the code. What we are really saying is that our first argument, uf
, needs to have the following type:
interface {
Find(int) (*User, error)
}
If we wanted we could even replace this code with an explicitly declared type:
type userFinder interface {
Find(int) (*User, error)
}
func doThing(uf userFinder) {
// ...
user = uf.Find(id)
// ...
}
Note: In practice this is often overkill to declare the type userFinder
unless we are using this interface more than once.
Now regardless of what is passed into the doThing
function, the only method accessible on the uf
variable is the Find
method. Unlike in Java, we also never had to explicitly state that our PsqlUserStore
implements this interface. Go’s duck typing handles those details and let’s us get on with our coding.
The last part - the fact that we never had to explicitly state that this interface was implemented - is what makes this plausible in Go and not in Java. It is a ton of work to write code with explicit behavior requirements in Java, but in Go it is incredibly easy. As a result, you will probably run into some code at some point that uses anonymous interfaces (is that the correct term? I think so…) that clearly articulate what methods are needed by a specific function.
This approach can also be used when defining the fields in a struct. For example, imagine we wanted to define an http.Handler
for processing purchases made by a user. We could write the following code to make it happen:
type Handler struct {
userService interface {
Find(id int) *User
}
orderService interface {
Create(*Cart) *Order
MarkPaid(*Order, *Charge)
}
paymentService interface {
Charge(*User, *Order) (*Charge, error)
}
mailService interface {
PaymentFailed(*User, error)
PaymentReceipt(*User, *Order, *Charge)
}
}
func (h *Handler) ServeHTTP(w http.ResponseWrite, r *http.Request) {
userID := parseUserID(r)
cart := parseCart(r)
user := h.userService.Find(userID)
order := h.orderService.Create(cart)
charge, err := h.paymentService.Charge(user, order)
if err != nil {
h.mailService.PaymentFailed(user, err)
http.Error(...)
return
}
h.mailService.PaymentReceipt(user, order, charge)
h.orderService.MarkPaid(order, charge)
// ...
}
The actual implementations of each of these interfaces might have a more methods than what we are defining in our interfaces. For instance, the mailService
implementation might look like this:
type MailService struct {}
func (ms *MailService) Welcome(*User)
func (ms *MailService) ForgotPassword(*User)
func (ms *MailService) PaymentFailed(*User, error)
func (ms *MailService) PaymentReceipt(*User, *Order, *Charge)
func (ms *MailService) DeliveryNotification(*User, *Order)
// ...
Despite the fact that the MailService
can do a lot of things, we have made it clear in our code that we only care about a small subset of the methods, and we did so with relatively minimal effort.
Duck typing, specifically taking advantage of it in the way we saw with the http handle rabove, is a seemingly minor detail that can have a major impact on the sustainability of our code. For instance:
MailService
type.MailService
or other similar types. They simply need to become familiar with a small subset of the methods.MailService
methods. Instead we would just need the two used by our handler - PaymentFailed
and PaymentReceipt
.In summary, duck typing doesn’t just change the order in which we write an implementation and an interface. Duck typing gives us the power to clearly articulate what behavior each piece of our code depends upon, and that is one of the many superpowers of interfaces in Go.
Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.
You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.
Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.
Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.
Related articles
Spread the word
Did you find this page helpful? Let others know about it!
Sharing helps me continue to create both free and premium Go resources.
Want to discuss the article?
See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!
You can reach me via email or via twitter.
©2018 Jonathan Calhoun. All rights reserved.