Interfaces are one of my favorite concepts in programming. It’s a clean way to define contract and decouple the implementation parts from code.

Interfaces indicate what a class or struct should do, or define the type of methods which a class or struct should implement. We can refer an interface and understand how the different parts used by a code block should behave.

I mostly use it to define segments of code, like http handler, service, storage to indicate that a segment is going to recieve some implementation, which will have certain methods which can be used in them and worry about the implementation later.

Example

A storage interface is a common usecase, where we can define the shape of the functions which a storage should have.

type Storage interface {
    Save(data string) error
    Read() (string, error)
    List() ([]string, error)
    Delete() error
}

The above interface indicates that a storage ( doesn’t matter where you store ) should have the following methods. And it will be used by any code block that’s referencing it like,

func saveData(s Storage, data string) error {
    return s.Save(data)
}

func readData(s Storage) (string, error) {
    return s.Read()
}

func listData(s Storage) ([]string, error) {
    return s.List()
}

func deleteData(s Storage) error {
    return s.Delete()
}

As you can see, it seperates the declaration and the implementation.

  • This makes it easier to test the code without having an actual implementation, you can create a mock implementation and use it for testing.
  • Another advantage is that, we can replace the implemented piece of code with another implementation without changing the code that uses it.

Is it good for performance? depends on the language and often in the implementation. The performance depends on static or dynamic dispatch of the language.

  • Dynamic dispatch: The method to be called is determined at runtime.
  • Static dispatch: The method to be called is determined at compile time.
Language Interface Behavior Dispatch Type
Rust Traits can be used for compile-time polymorphism. Static Dispatch
Go Interfaces are implicitly implemented. Dynamic Dispatch
C# Interfaces and abstract classes define contracts. Dynamic Dispatch
TypeScript Interfaces and abstract classes are used. Static Dispatch (Type Checking), Dynamic Dispatch (Runtime)
Swift Protocols can be used with or without dynamic types. Both (Static and Dynamic Dispatch)
Kotlin Interfaces and abstract classes are used. Dynamic Dispatch
Scala Traits and abstract classes are used. Both (Static and Dynamic Dispatch)
Java Interfaces are used to define a contract. Dynamic Dispatch
C++ Templates and virtual functions are used. Both (Static and Dynamic Dispatch)

Either way, using interfaces is a good practice especially between segments which should be tested independently and can be replaced with another implementation.