Interfaces Basics — Tasks¶
Easy 🟢¶
Task 1¶
Create an Animal interface (Sound() string) and have Dog and Cat satisfy it.
Task 2¶
Create a Color type that satisfies Stringer.
Task 3¶
Create a custom error type: NotFoundError{ID string}.
Task 4¶
Create a MyResource type that satisfies the Closer interface.
Task 5¶
Greeter interface — Hello() string. Multiple implementations.
Medium 🟡¶
Task 6¶
Shape interface (Area, Perimeter). Circle, Rectangle, and Triangle satisfy it.
Task 7¶
Logger interface with three implementations: ConsoleLogger, FileLogger, MultiLogger.
Task 8¶
Custom slice type that satisfies sort.Interface.
Task 9¶
BufferWriter type that satisfies io.Writer.
Task 10¶
Repository interface with MockRepo and PgRepo implementations.
Hard 🔴¶
Task 11¶
Decorator pattern: LoggingRepo, CachingRepo — should decorate another Repo.
Task 12¶
Specification pattern — Spec interface { IsSatisfiedBy(u User) bool }. And, Or, Not combinators.
Task 13¶
Strategy pattern: Sorter interface with three algorithms.
Task 14¶
Pipeline: Stage interface { Process(in any) any }. Chain the stages together.
Task 15¶
Plugin system — load plugins at runtime (via an interface).
Solutions¶
Solution 1¶
type Animal interface { Sound() string }
type Dog struct{}
func (Dog) Sound() string { return "Woof" }
type Cat struct{}
func (Cat) Sound() string { return "Meow" }
Solution 2¶
type Color struct{ R, G, B uint8 }
func (c Color) String() string { return fmt.Sprintf("rgb(%d,%d,%d)", c.R, c.G, c.B) }
Solution 3¶
type NotFoundError struct{ ID string }
func (e *NotFoundError) Error() string { return "not found: " + e.ID }
Solution 4¶
type MyResource struct{ name string }
func (r *MyResource) Close() error {
fmt.Println("closing", r.name)
return nil
}
Solution 5¶
type Greeter interface { Hello() string }
type English struct{}
func (English) Hello() string { return "Hello" }
type Uzbek struct{}
func (Uzbek) Hello() string { return "Salom" }
type French struct{}
func (French) Hello() string { return "Bonjour" }
Solution 6¶
type Shape interface { Area() float64; Perimeter() float64 }
type Circle struct{ R float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.R }
type Rectangle struct{ W, H float64 }
func (r Rectangle) Area() float64 { return r.W * r.H }
func (r Rectangle) Perimeter() float64 { return 2 * (r.W + r.H) }
type Triangle struct{ A, B, C float64 }
func (t Triangle) Area() float64 {
s := t.Perimeter() / 2
return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}
func (t Triangle) Perimeter() float64 { return t.A + t.B + t.C }
Solution 7¶
type Logger interface { Log(level, msg string) }
type ConsoleLogger struct{}
func (ConsoleLogger) Log(level, msg string) {
fmt.Printf("[%s] %s\n", level, msg)
}
type FileLogger struct{ f *os.File }
func (l *FileLogger) Log(level, msg string) {
fmt.Fprintf(l.f, "[%s] %s\n", level, msg)
}
type MultiLogger struct{ loggers []Logger }
func (m *MultiLogger) Log(level, msg string) {
for _, l := range m.loggers { l.Log(level, msg) }
}
Solution 8¶
type ByName []Person
func (a ByName) Len() int { return len(a) }
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
sort.Sort(ByName(people))
Solution 9¶
type BufferWriter struct{ buf []byte }
func (b *BufferWriter) Write(p []byte) (n int, err error) {
b.buf = append(b.buf, p...)
return len(p), nil
}
func (b *BufferWriter) String() string { return string(b.buf) }
Solution 10¶
type User struct{ ID, Name string }
type Repository interface {
Find(id string) (*User, error)
Save(u *User) error
}
type MockRepo struct{ users map[string]*User }
func (r *MockRepo) Find(id string) (*User, error) {
if u, ok := r.users[id]; ok { return u, nil }
return nil, errors.New("not found")
}
func (r *MockRepo) Save(u *User) error {
r.users[u.ID] = u
return nil
}
type PgRepo struct{ db *sql.DB }
func (r *PgRepo) Find(id string) (*User, error) { /* SQL */ return nil, nil }
func (r *PgRepo) Save(u *User) error { /* SQL */ return nil }
Solution 11¶
type LoggingRepo struct{ inner Repository }
func (l *LoggingRepo) Find(id string) (*User, error) {
log.Println("Find:", id)
return l.inner.Find(id)
}
func (l *LoggingRepo) Save(u *User) error {
log.Println("Save:", u.ID)
return l.inner.Save(u)
}
type CachingRepo struct {
inner Repository
cache map[string]*User
mu sync.Mutex
}
func (c *CachingRepo) Find(id string) (*User, error) {
c.mu.Lock()
if u, ok := c.cache[id]; ok { c.mu.Unlock(); return u, nil }
c.mu.Unlock()
u, err := c.inner.Find(id)
if err != nil { return nil, err }
c.mu.Lock()
c.cache[id] = u
c.mu.Unlock()
return u, nil
}
func (c *CachingRepo) Save(u *User) error {
c.mu.Lock(); delete(c.cache, u.ID); c.mu.Unlock()
return c.inner.Save(u)
}
Solution 12¶
type Spec interface { IsSatisfiedBy(u User) bool }
type AgeAbove struct{ Min int }
func (s AgeAbove) IsSatisfiedBy(u User) bool { return u.Age >= s.Min }
type IsActive struct{}
func (s IsActive) IsSatisfiedBy(u User) bool { return u.Active }
type And struct{ A, B Spec }
func (s And) IsSatisfiedBy(u User) bool { return s.A.IsSatisfiedBy(u) && s.B.IsSatisfiedBy(u) }
type Or struct{ A, B Spec }
func (s Or) IsSatisfiedBy(u User) bool { return s.A.IsSatisfiedBy(u) || s.B.IsSatisfiedBy(u) }
type Not struct{ S Spec }
func (s Not) IsSatisfiedBy(u User) bool { return !s.S.IsSatisfiedBy(u) }
Solution 13¶
type Sorter interface { Sort([]int) }
type BubbleSort struct{}
func (BubbleSort) Sort(xs []int) {
n := len(xs)
for i := 0; i < n; i++ {
for j := 0; j < n-i-1; j++ {
if xs[j] > xs[j+1] { xs[j], xs[j+1] = xs[j+1], xs[j] }
}
}
}
type QuickSort struct{}
func (QuickSort) Sort(xs []int) {
sort.Ints(xs) // delegate
}
type StdSort struct{}
func (StdSort) Sort(xs []int) { sort.Ints(xs) }
Solution 14¶
type Stage interface { Process(in any) any }
type UpperCase struct{}
func (UpperCase) Process(in any) any { return strings.ToUpper(in.(string)) }
type AddPrefix struct{ Prefix string }
func (s AddPrefix) Process(in any) any { return s.Prefix + in.(string) }
type Pipeline struct{ stages []Stage }
func (p *Pipeline) Add(s Stage) *Pipeline { p.stages = append(p.stages, s); return p }
func (p *Pipeline) Run(in any) any {
for _, s := range p.stages { in = s.Process(in) }
return in
}
// p := &Pipeline{}
// p.Add(UpperCase{}).Add(AddPrefix{Prefix: ">> "})
// p.Run("hello") // ">> HELLO"
Solution 15 (abridged)¶
type Plugin interface {
Name() string
Execute(ctx context.Context) error
}
type Registry struct{ plugins map[string]Plugin }
func NewRegistry() *Registry { return &Registry{plugins: map[string]Plugin{}} }
func (r *Registry) Register(p Plugin) { r.plugins[p.Name()] = p }
func (r *Registry) Run(ctx context.Context, name string) error {
p, ok := r.plugins[name]
if !ok { return errors.New("plugin not found") }
return p.Execute(ctx)
}