Go Fundamentals - Sample
Table of Contents
Chapter 8.10: Defining Interfaces
You can create a new interface in the Go type system by using the type
keyword, giving the new type a name, and then basing that new type on the interface
type, Listing 8.1.
type MyInterface interface {}
Interfaces define behavior, therefore they are only a collection of methods. Interfaces can have zero, one, or many methods.
The larger the interface, the weaker the abstraction. – Rob Pike
It is considered to be non-idiomatic to have large interfaces. Keep the number of methods per interface as small as possible, Listing 8.2. Small interfaces allow for easier interface implementations, especially when testing. Small interfaces also help us keep our functions and methods small in scope making them more maintainable and testable.
type MyInterface interface {
Method1()
Method2() error
Method3() (string, error)
}
It is important to note that interfaces are a collection of methods, not fields, Listing 8.3. In Go only structs have fields, however, any type in the system can have methods. This is why interfaces are limited to methods only.
// valid
type Writer interface {
Write(p []byte) (int, error)
}
// invalid
type Emailer interface {
Email string
}
Defining a Model Interface
Consider, again, the Insert
method for our data store, Listing 8.4. The method takes two arguments. The first argument is the ID of the model to be stored.
func (s *Store) Insert(id int, m any) error {
Insert
method.The second argument, in Listing 8.4, should be one of our data models. However, because we are using an empty interface, any type from int
to nil
may be passed in.
func Test_Store_Insert(t *testing.T) {
t.Parallel()
// create a store
s := &Store{
data: Data{},
}
exp := 1
// insert a non-valid type
err := s.Insert(exp, func() {})
if err != nil {
t.Fatal(err)
}
// retreive the type
act, err := s.Find(exp)
if err != nil {
t.Fatal(err)
}
// assert the returned value is a func()
_, ok := act.(func())
if !ok {
t.Fatalf("unexpected type %T", act)
}
}
$ go test -v
=== RUN Test_Store_Insert
=== PAUSE Test_Store_Insert
=== CONT Test_Store_Insert
--- PASS: Test_Store_Insert (0.00s)
PASS
ok demo 3.450s
--------------------------------------------------------------------------------
Go Version: go1.23.0
Insert
method.To prevent types, such as a function definition, Listing 8.5, that aren't an expected data model, we can define an interface to solve this problem. Since the Insert
function needs an ID for insertion, we can use that as the basis for an interface.
type Model interface {
ID() int
}
Model
interface.To implement the Model
interface, Listing 8.6, a type must have a ID() int
method. We can cleanup the Insert
method's definition by accepting a single argument, the Model
interface, Listing 8.7.
func (s *Store) Insert(m Model) error {
Insert
method to accept a Model
interface.Now, the compiler and/or runtime will reject any type that, such as string
, []byte
, and func()
, that doesn't have a ID() int
method, Listing 8.8.
func Test_Store_Insert(t *testing.T) {
t.Parallel()
// create a store
s := &Store{}
exp := 1
// insert a non-valid type
err := s.Insert(func() {})
if err != nil {
t.Fatal(err)
}
// retreive the type
act, err := s.Find(exp)
if err != nil {
t.Fatal(err)
}
// assert the returned value is a func()
_, ok := act.(func())
if !ok {
t.Fatalf("unexpected type %T", act)
}
}
$ go test -v
FAIL demo [build failed]
# demo [demo.test]
./store_test.go:15:18: cannot use func() {} (value of type func()) as Model value in argument to s.Insert: func() does not implement Model (missing method ID)
./store_test.go:27:11: impossible type assertion: act.(func())
func() does not implement Model (missing method ID)
--------------------------------------------------------------------------------
Go Version: go1.23.0
Model
.Implementing the Interface
Finally, let's create a new type, User
, that implements the Model
interface, Listing 8.9.
type User struct {
UID int
}
func (u User) ID() int
User
type implements the Model
interface.When we update the tests, Listing 8.10, to use the User
type, our tests now pass.
func Test_Store_Insert(t *testing.T) {
t.Parallel()
// create a store
s := &Store{
data: Data{},
}
// create a user
exp := User{UID: 1}
// insert the user
err := s.Insert(exp)
if err != nil {
t.Fatal(err)
}
// retreive the user
act, err := s.Find(exp.UID)
if err != nil {
t.Fatal(err)
}
// assert the returned value is a user
actu, ok := act.(User)
if !ok {
t.Fatalf("unexpected type %T", act)
}
// assert the returned user is the same as the inserted user
if exp.UID != actu.UID {
t.Fatalf("expected %v, got %v", exp, actu)
}
}
$ go test -v
=== RUN Test_Store_Insert
=== PAUSE Test_Store_Insert
=== CONT Test_Store_Insert
--- PASS: Test_Store_Insert (0.00s)
PASS
ok demo 2.861s
--------------------------------------------------------------------------------
Go Version: go1.23.0
User
type.