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
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With