Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Export interfaces instead of structs from golang packages

Tags:

go

Below you can find three different ways of calling a method Name() of a customer struct. The result is exactly the same but each one of the three package exports different things:

package main

import (
    "customer1"
    "customer2"
    "customer3"
    "fmt"
    "reflect"
)

func main() {

    c1 := customer1.NewCustomer("John")
    fmt.Println(c1.Name())

    c2 := customer2.NewCustomer("John")
    fmt.Println(c2.Name())

    c3 := customer3.NewCustomer("John")
    fmt.Println(c3.Name())
}

Output

John
John
John

customer1.go (Export Customer struct and Name() method)

package customer1

type Customer struct {
    name string
}

func NewCustomer(name string) * Customer{
    return &Customer{name: name}
}

func (c *Customer) Name() string {
    return c.name
}

customer2.go (Do not export customer struct. Export only Name() method)

package customer2

type customer struct {
    name string
}

func NewCustomer(name string) *customer {
    return &customer{name: name}
}

func (c *customer) Name() string {
    return c.name
}

customer3.go (Do not export customer struct. Export Customer Interface)

package customer3

type Customer interface {
    Name() string
}
type customer struct {
    name string
}

func NewCustomer(name string) Customer {
    return &customer{name: name}
}

func (c *customer) Name() string {
    return c.name
}

My question is which approach you would recommend and why? Which is better in terms of extensibility and maintainability? Which one you would use for a large project?

It seems that customer3 approach is officially discouraged (// DO NOT DO IT!!!) as you can read here https://github.com/golang/go/wiki/CodeReviewComments#interfaces

like image 810
psampaz Avatar asked Jun 25 '18 20:06

psampaz


1 Answers

Interfaces in Go work (and are used) a bit differently that what you'd expect if you come from other languages such as Java.

In Go, the object implementing the interface does not need to explicitly say it implements it.

That has subtle ramifications, such as the consumer of a type being able to decouple from the implementation even if the implementing side did not bother (or think about) creating an interface in the first place.

So, the idiomatic way in Go would be to use a variation of your first approach.

You'd define customer1.go exactly as you did (so implementation of the type is as simple as possible).

Then if necessary you can de-couple in the consumer (your main package in this case) by defining an interface there:

type Customer interface {
    Name() string
}

func main() {

    var c1 Customer
    c1 := customer1.NewCustomer("John")
    fmt.Println(c1.Name())

}

That way, your main implementation can work with any type that has a Name() method, even if the package implementing the type in the first place did not think about that need.

To achieve extensibility, this is usually also applied for functions you export that receive parameters.

If you are exporting a function like this:

func PrintName(customer Customer) {
    fmt.Println(customer.Name())
}

Then that function can be invoked with any object that implements Customer (any of your implementations would work for example).

like image 141
eugenioy Avatar answered Nov 07 '22 17:11

eugenioy