interface all the things
TRANSCRIPT
Intro to Interfaces
type Gopher interface { Nod() Wink()}
Could be read as, a Gopher is something that has these behaviours / actions:
Ability to nod
Ability to wink
Intro to Interfaces
Lets implement Nod() and Wink():
type SingGopher struct{}
func (g *SingGopher) Nod() { fmt.Println("can nod")}
func (g *SingGopher) Wink() { fmt.Println("wink also can")}
By having these methods, SingGopher implicitly satisfies the Gopher interface.
i.e. don't need to explicitly state that SingGopher implements the interface.
Intro to Interfaces
With the interface satisfied, we can assign our SingGopher to the Gopher type.
func play(gopher Gopher) { gopher.Nod() gopher.Wink()}
func main() { play(&SingGopher{})} Run
Intro to Interfaces
What if a method was missing?
Think in terms of method sets: set of interface ⊆ set of concrete type
func main() { play(&SingGopher{})} Run
Why use interfaces? Abstract and avoid repetition
e.g. Gopher processing code (business logic)
func pipeline(gophers ...Gopher) { for _, gopher := range gophers { play(gopher) }}
As new gophers are added, no change is required.
Note: Can define functions and methods that take interfaces, but can't definemethods on the interface (e.g. gopher.Play())
func main() { pipeline(&SingGopher{}, &YodaGopher{})} Run
Why use interfaces? Combine simple units to achieve complexity
Example of standard library's io.Reader interface:
type Reader interface { Read(p []byte) (n int, err error)}
Functions/methods that take io.Reader arguments are indifferent to where datais coming from. e.g. os.File, bytes.Buffer, net.Conn, http.Request.Body
// jsonfunc NewDecoder(r io.Reader) *Decoder
// gzipfunc NewReader(r io.Reader) (*Reader, error)
Chain for complexity:
response, _ := client.Do(request)raw, _ := gzip.NewReader(response.Body)json.NewDecoder(raw).Decode(&data)
Why use interfaces? Sharing patterns
Dropbox has a interesting caching library @ github.com/dropbox/godropbox(https://godoc.org/github.com/dropbox/godropbox/caching)
Patterns of caching / managing storage
func NewCacheOnStorage(cache, storage Storage) Storagefunc NewRateLimitedStorage(storage Storage, maxConcurrency int) Storage
Developers gain access via implementing Storage on their own:
type Storage interface { Get(...) (interface{}, error) Set(...) error Delete(...) error ...}
Tap into / contribute to the growing set of community resources
Why use interfaces? Testing
If our functions have interface args, we can test them with mock implementations
i.e. use a FakeGopher to test our code that depends on Gopher
type FakeGopher struct { Nodded bool Winked bool}
func (g *FakeGopher) Nod() { g.Nodded = true}
func (g *FakeGopher) Wink() { g.Winked = true}
func TestPlay() { g := &FakeGopher{} g.ShowState() play(g) g.ShowState()}
Run
Why use interfaces? Flexibility to change your lib/infra decisions
In one of our apps, we use a 3rd party logging library, logrus.
Avoid our codebase being coupled to it, by having an interface for logging.
type Logger interface { Info(args ...interface{}) Warn(args ...interface{}) Error(args ...interface{}) Infof(args ...interface{}) Warnf(args ...interface{}) Errorf(args ...interface{})}
e.g. Publisher w/ Publish() (to queue) & Mailer & Mail() (3rd party service)
Seize opportunities to decrease coupling / increase options.
Better to do earlier than later.
Why use interfaces? Flexibility for package consumers
type Storage interface { Get(...) (Kites, error) Add(...) error Update(...) error Delete(...) error Upsert(...) error}
Interesting microservices project github.com/koding/kite (https://github.com/koding/kite)
Storage interface with Postgres and Etcd implementations
Useful here to give the consumer options
Maybe unnecessary in your own app
Why use interfaces? Very natural fit for some problems
When standardisation is essential to design
The method set creates the standard
Project to create and manage Docker hosts across providersgithub.com/docker/machine (https://github.com/docker/machine)
type Driver interface { Create() error GetIP() (string, error) Start() error Stop() error ...}
Implementations for Amazon, Google, Digital Ocean, Azure, OpenStack, Rackspace+
Tips: Check out the standard library
100+ interfaces
errorStringerHandlerResponseWritersort.InterfaceReader, Writer, ReadWriter,Marshaler, UmarshalerTB (tests, benchmarks)
Convention is to name the one method interface Method-er
Encouragement to keep interfaces small, but it isn't a hard rule
Smaller = less work to satisfy
Where your code can be compatible with standard library / community interfaces,will be more easily useable by others
e.g. middleware that takes a Handler and returns a Handler
Tips: Use embedding to keep interfaces small
Compose larger interfaces out of smaller ones
type Reader interface { Read(p []byte) (n int, err error)}
type Writer interface { Write(p []byte) (n int, err error)}
type ReadWriter interface { Reader Writer}
When method invoked, the receiver is the inner type, not outer
Will reduce code complexity, encourage simpler implementations
Tips: Type assert for accessing different functionality
Assume some gophers are also coders which can Code()
type Coder interface { Code()}
Runtime type assert to see whether the Gopher is a Coder & call Code()
func writeCode(gopher Gopher) {
// if our Gopher satisfies Coder, then type assert if coder, ok := gopher.(Coder); ok { coder.Code() }}
Note: the coder can't call Nod() or Wink().
Considered an idiom in Go to convert type to access different methods.
Tips: Interfaces and nil
What is an interface? Implemented as a type and a value
If type is set, and value is a nil pointer, the interface is not nil
When implementing custom errors - return nil instead of interfaces with nil values.
func main() {
var singGopher *SingGopher var gopher Gopher var gopher2 Gopher = singGopher
fmt.Println("values:") fmt.Println("singGopher: ", singGopher, singGopher == nil) fmt.Println("gopher: ", gopher, gopher == nil) fmt.Println("gopher2: ", gopher2, gopher2 == nil)
fmt.Println("types: ") fmt.Printf("singGopher: %#v\n", singGopher) fmt.Printf("gopher: %#v\n", gopher) fmt.Printf("gopher2: %#v\n", gopher2)
} Run
Thank you
Jonathan GomezEngineer, [email protected] (mailto:[email protected])
@jonog (http://twitter.com/jonog)