Presented at Singapore Gophers Meetup - 12 May 2015
Overview of Go's interfaces:
- What are interfaces?
- Why are they awesome?
- Tips and things to watch out for
Source: https://github.com/jonog/interface-all-the-things
Slides: http://go-talks.appspot.com/github.com/jonog/interface-all-the-things/interface-all-the-things.slide
4. Intro to Interfaces
Lets implement Nod()and Wink():
typeSingGopherstruct{}
func(g*SingGopher)Nod(){
fmt.Println("cannod")
}
func(g*SingGopher)Wink(){
fmt.Println("winkalsocan")
}
By having these methods, SingGopherimplicitly satisfies the Gopherinterface.
i.e. don't need to explicitly state that SingGopherimplements the interface.
5. Intro to Interfaces
With the interface satisfied, we can assign our SingGopherto the Gophertype.
funcplay(gopherGopher){
gopher.Nod()
gopher.Wink()
}
funcmain(){
play(&SingGopher{})
} Run
6. Intro to Interfaces
What if a method was missing?
Think in terms of method sets: set of interface ⊆ set of concrete type
funcmain(){
play(&SingGopher{})
} Run
8. Why use interfaces? Abstract and avoid repetition
e.g. Gopherprocessing code (business logic)
funcpipeline(gophers...Gopher){
for_,gopher:=rangegophers{
play(gopher)
}
}
As new gophers are added, no change is required.
Note: Can define functions and methods that take interfaces, but can't define
methods on the interface (e.g. gopher.Play())
funcmain(){
pipeline(&SingGopher{},&YodaGopher{})
} Run
9. Why use interfaces? Combine simple units to achieve complexity
Example of standard library's io.Readerinterface:
typeReaderinterface{
Read(p[]byte)(nint,errerror)
}
Functions/methods that take io.Readerarguments are indifferent to where data
is coming from. e.g. os.File, bytes.Buffer, net.Conn, http.Request.Body
//json
funcNewDecoder(rio.Reader)*Decoder
//gzip
funcNewReader(rio.Reader)(*Reader,error)
Chain for complexity:
response,_:=client.Do(request)
raw,_:=gzip.NewReader(response.Body)
json.NewDecoder(raw).Decode(&data)
10. 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
funcNewCacheOnStorage(cache,storageStorage)Storage
funcNewRateLimitedStorage(storageStorage,maxConcurrencyint)Storage
Developers gain access via implementing Storageon their own:
typeStorageinterface{
Get(...)(interface{},error)
Set(...)error
Delete(...)error
...
}
Tap into / contribute to the growing set of community resources
11. Why use interfaces? Testing
If our functions have interface args, we can test them with mock implementations
i.e. use a FakeGopherto test our code that depends on Gopher
typeFakeGopherstruct{
Noddedbool
Winkedbool
}
func(g*FakeGopher)Nod(){
g.Nodded=true
}
func(g*FakeGopher)Wink(){
g.Winked=true
}
funcTestPlay(){
g:=&FakeGopher{}
g.ShowState()
play(g)
g.ShowState()
} Run
12.
13. 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.
typeLoggerinterface{
Info(args...interface{})
Warn(args...interface{})
Error(args...interface{})
Infof(args...interface{})
Warnf(args...interface{})
Errorf(args...interface{})
}
e.g. Publisherw/ Publish()(to queue) & Mailer& Mail()(3rd party service)
Seize opportunities to decrease coupling / increase options.
Better to do earlier than later.
14. Why use interfaces? Flexibility for package consumers
typeStorageinterface{
Get(...)(Kites,error)
Add(...)error
Update(...)error
Delete(...)error
Upsert(...)error
}
Interesting microservices project github.com/koding/kite(https://github.com/koding/kite)
Storageinterface with Postgresand Etcdimplementations
Useful here to give the consumer options
Maybe unnecessary in your own app
15. 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 providers
github.com/docker/machine(https://github.com/docker/machine)
typeDriverinterface{
Create()error
GetIP()(string,error)
Start()error
Stop()error
...
}
Implementations for Amazon, Google, Digital Ocean, Azure, OpenStack, Rackspace
+
16. Tips: Check out the standard library
100+ interfaces
error
Stringer
Handler
ResponseWriter
sort.Interface
Reader,Writer,ReadWriter,
Marshaler,Umarshaler
TB(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 Handlerand returns a Handler
17. Tips: Use embedding to keep interfaces small
Compose larger interfaces out of smaller ones
typeReaderinterface{
Read(p[]byte)(nint,errerror)
}
typeWriterinterface{
Write(p[]byte)(nint,errerror)
}
typeReadWriterinterface{
Reader
Writer
}
When method invoked, the receiver is the inner type, not outer
Will reduce code complexity, encourage simpler implementations
18. Tips: Type assert for accessing different functionality
Assume some gophers are also coders which can Code()
typeCoderinterface{
Code()
}
Runtime type assert to see whether the Gopheris a Coder& call Code()
funcwriteCode(gopherGopher){
//ifourGophersatisfiesCoder,thentypeassert
ifcoder,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.
19. 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.
funcmain(){
varsingGopher*SingGopher
vargopherGopher
vargopher2Gopher=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:%#vn",singGopher)
fmt.Printf("gopher:%#vn",gopher)
fmt.Printf("gopher2:%#vn",gopher2)
} Run