Created
May 11, 2021 23:00
-
-
Save miroswan/ecdb6c0e52b9a7c87e2b74f3f5811118 to your computer and use it in GitHub Desktop.
Dependency Inversion and Interface Segregation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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) | |
} | |
} | |
} | |
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.
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
is useful to enforce that a struct implements correctly an interface.