DEV Community

Cover image for Golang Master Class: Interface Driven Design.
Sk
Sk

Posted on

Golang Master Class: Interface Driven Design.

You want to build large, scalable Golang projects?
Interface-driven design is non-negotiable.

This is where you separate the dabblers from the builders.

Interface-driven design is to Go what polymorphism is to other strongly typed languages

but without the mess and madness.


In traditional OOP, classes define both data and behavior, and they can inherit from each other:

class Animal {
  data
  behaviour
}
Enter fullscreen mode Exit fullscreen mode

But in Go, interfaces define only behavior. No data. Just function signatures.

type Animal interface {
  walk()
}
Enter fullscreen mode Exit fullscreen mode

If something quacks like a duck (implements the walk method), then it's an Animal.
That’s duck typing.

Full Code:

git clone https://github.com/sklyt/goway.git
Enter fullscreen mode Exit fullscreen mode

But why does this matter?

Let’s rewind for a second, interface-driven design makes way more sense once you understand what it means to be strongly typed.

In a statically typed language e.g c++, a container can only hold one type:

class Bird {}

std::vector<Bird> zoo;
Enter fullscreen mode Exit fullscreen mode

This zoo only accepts Birds. So if you need an Alligator, you’re stuck:

class Alligator {}

std::vector<Alligator> zoo2;
Enter fullscreen mode Exit fullscreen mode

Now we have two zoos. Not ideal.

Enter polymorphism:

If both Bird and Alligator inherit from Animal, and Animal is the common base class they qualify.

class Animal {}
class Bird : public Animal {}
class Alligator : public Animal {}

std::vector<Animal> zoo;
Enter fullscreen mode Exit fullscreen mode

Now you can toss both in the same zoo.

But there's a catch:
They’re tightly coupled to Animal. Any changes to the base class ripple through all the subclasses. That’s not great for scaling.


Go takes a different path.

Go doesn’t do inheritance. It’s all about composition and contracts.

Duck typing isn’t a relationship. It’s a contract.


Duck Typing 101

type Animal interface {
  walk()
}

// Type is on the right in Go (yeah I know 😭)
func MakeWalk(a *Animal) {
  a.walk()
}
Enter fullscreen mode Exit fullscreen mode

That’s it. Anything that wants to be an Animal just needs to implement walk().

We don’t care about its fields, history. Just that it walks.

type Bird struct {
  type_ string
  name  string
}

func (b *Bird) walk() {
  fmt.Println("I am walking")
}
Enter fullscreen mode Exit fullscreen mode

Now we can do:

MakeWalk(&Bird{name: "Tweeter", type_: "weird_bird"})
Enter fullscreen mode Exit fullscreen mode

This is how we build LEGO-style systems. Plug-and-play components.


Real Example: Local and Online Storage

Let’s bring it to life with a real-world example.

Step 1: Set up a new project

go mod init goway
Enter fullscreen mode Exit fullscreen mode

Structure:

internal/
  storage/
    storage.go 
    local.go
    online.go
main.go
Enter fullscreen mode Exit fullscreen mode

storage.go

package storage

type Storage interface {
  Save(key string, data []byte) error
  Load(key string) ([]byte, error)
}
Enter fullscreen mode Exit fullscreen mode

That’s our contract.


local.go

type LocalStore struct {
  Path  string
  store map[string][]byte
}

func (l *LocalStore) Save(key string, val []byte) error {
  fmt.Println("Saving " + key + " to " + l.Path)
  return nil
}

func (l *LocalStore) Load(key string) ([]byte, error) {
  fmt.Println("Loading " + key + " from " + l.Path)
  data, ok := l.store[key]
  if !ok {
    return nil, ErrNotFound
  }
  return data, nil
}
Enter fullscreen mode Exit fullscreen mode

Implements Save and Load → It's a Storage.


online.go

type OnlineStore struct {
  URL string
}

func (o *OnlineStore) Save(key string, data []byte) error {
  fmt.Println("Uploading " + key + " to " + o.URL)
  return nil
}

func (o *OnlineStore) Load(key string) ([]byte, error) {
  fmt.Println("Downloading " + key + " from " + o.URL)
  return []byte("..."), nil
}
Enter fullscreen mode Exit fullscreen mode

Also implements Save and Load → It’s a Storage.


Now the fun part: main.go

func useStorageGet(store storage.Storage, key string) ([]byte, error) {
  data, err := store.Load(key)
  if err != nil {
    return nil, fmt.Errorf("get failed: %w", err)
  }
  return data, nil
}

func main() {
  local := storage.LocalStore{Path: "./data"}
  online := storage.OnlineStore{URL: "http://"}

  res, err := useStorageGet(&local, "foo")
  if err != nil {
    // cache miss, fallback to online
    res, err = useStorageGet(&online, "foo")
    if err != nil {
      // handle error
    }
  }

  fmt.Println(string(res))
}
Enter fullscreen mode Exit fullscreen mode

One function. Two implementations.
No switch statements. No if-else chains. Just clean, swappable behavior.

That’s the foundation of composition. And we’re not even using Go’s full power yet!


What’s next?

This is just the start.

Coming up in future posts:

  • Constructor functions
  • Struct embedding
  • Functional options
  • Dependency injection (for real)
  • Channels (concurrency magic)

Together, these techniques will take your Go projects from:

“eh it runs”
to

quality work

Top comments (2)

Collapse
 
avanichols_dev profile image
Ava Nichols

Great walk-through, but if duck typing is a “contract,” does that mean my code needs a good lawyer before it implements an interface? Just kidding—I do love how Go skips the class inheritance therapy sessions. Still, it feels a bit weird that I can make anything an Animal as long as it “walks.” My fridge might be eligible soon!

Collapse
 
sfundomhlungu profile image
Sk

I actually like that analogy! I guess you’re the lawyer in this case 🤣
If it walks like an Animal, talks like an Animal… case closed.

Image description

“Still, it feels a bit weird that I can make anything an Animal as long as it ‘walks.’ My fridge might be eligible soon!”

I totally agree! The looser the language, the more it all depends on your creativity, and your constraints.

It’s kinda like frameworks.
Angular is strict (very OOP vibes), while React is more like duck typing: “Just make it work and don’t break anything.”

But yeah, really great observation! Now I can’t unsee my fridge implementing Walkable 😂