Skip to content

Instantly share code, notes, and snippets.

@miroswan
Created May 11, 2021 23:00
Show Gist options
  • Save miroswan/ecdb6c0e52b9a7c87e2b74f3f5811118 to your computer and use it in GitHub Desktop.
Save miroswan/ecdb6c0e52b9a7c87e2b74f3f5811118 to your computer and use it in GitHub Desktop.
Dependency Inversion and Interface Segregation
/*
This toy example explains how you can use dependency inversion and
interface segregation in Go. It allows your code to remain flexible,
unit testable, and better protected from upstream changes. In the
last case, if a library author decides to break their own code in
ways that do not fit your design, you have the opportunity to
impelement the interface yourself or simply wrap their code in a way
that suites you, all while not having to change a single line in your
existing code that depends on it since you own the interface.
*/
package main
import (
"fmt"
"time"
)
// TOY API Implementation Providers
//
//
// FastDataFetcher fetches data in a KV store
type FastDataFetcher struct {
kv map[string]string
}
// Get the data
func (f *FastDataFetcher) Get(key string) (string, bool) {
value, found := f.kv[key]
return value, found
}
// SlowDataFetcher fetches data from a KV very slowly
type SlowDataFetcher struct {
sleepDuration time.Duration
kv map[string]string
}
// Get the data
func (s *SlowDataFetcher) Get(key string) (string, bool) {
time.Sleep(s.sleepDuration)
value, found := s.kv[key]
return value, found
}
// TOY API Consumer
//
//
// ErrKeyNotFound is returned when the key is not found
type ErrKeyNotFound struct {
key string
}
func (e ErrKeyNotFound) Error() string {
return fmt.Sprintf("Key not found: %s", e.key)
}
// DataFetcher is the interface that our DataPrinter will rely on
type DataFetcher interface {
// Get the data
Get(string) (string, bool)
}
// DataPrinter simply prints dat in the KV store by key
type DataPrinter struct {
dataFetcher DataFetcher
}
// PrintByKey to STDOUT
func (d *DataPrinter) PrintByKey(key string) error {
value, found := d.dataFetcher.Get(key)
if !found {
return ErrKeyNotFound{key: key}
}
fmt.Println(value)
return nil
}
func main() {
// construct both printers
fastDataPrinter := &DataPrinter{
dataFetcher: &FastDataFetcher{ // interface implicitly satisfied
kv: map[string]string{"type": "fast"},
},
}
slowDataPrinter := &DataPrinter{
dataFetcher: &SlowDataFetcher{ // interface implicitly satisfied
kv: map[string]string{"type": "slow"},
sleepDuration: time.Second,
},
}
// Do the actual work
for _, printer := range []*DataPrinter{fastDataPrinter, slowDataPrinter} {
if err := printer.PrintByKey("type"); err != nil {
panic(err)
}
}
}
@b-2-83
Copy link

b-2-83 commented May 12, 2021

var _ DataPrinter = &FastDataFetcher{}
var _ DataPrinter = &SlowDataFetcher{}

is useful to enforce that a struct implements correctly an interface.

@miroswan
Copy link
Author

miroswan commented May 12, 2021

@b-2-83

var _ DataPrinter = &FastDataFetcher{}
var _ DataPrinter = &SlowDataFetcher{}

is useful to enforce that a struct implements correctly an interface.

I think the pattern you're looking for here is:

var _ DataPrinter = (*FastDataFetcher)(nil)
var _ DataPrinter = (*SlowDataFetcher)(nil)

But yes, this can be helpful.

@b-2-83
Copy link

b-2-83 commented May 12, 2021

ok I didn't know that syntax, certainly because permit to many way to create variables.

I found also this solution, but I don't use much new. : var _ DataPrinter = new(FastDataFetcher)

But I find &FastDataFetcher{} clearer because I see that I create an object and then ask for the pointer.

I understand it will create an object for nothing, but normally it should not be present in the binary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment