The adapter pattern in Go

This is a continuation of the common design patterns I found in my old code series, which I started in a previous post.

A while back I was playing around with a Raspberry Pi 4 and some temperature sensors. Some of the sensors I was using were exposing data over the SPI communication interface and some were exposing it over the I2C interface.

But I didn’t want the client app that was reading the temperature to change every time I swapped a sensor, so I ended up making a simple wrapper that exposed a single method to read the temperature and which could be initialized with different protocols.

This is the adapter design pattern. It’s a design pattern that allows the interface of a class or object to be used as another interface.

This is pretty common in hardware drivers. Take printers for instance. Most printers can be used either via USB or serial connections or over the network.

It’s also a frequently used pattern in ORM libraries (ActiveRecord, GORM, etc), because it allows for connections and queries to different data backends (databases) while keeping the same client interface.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bkd := &SQLBackend{}
db := &Conn{Backend: bkd}

db.Query()

...

type Conn struct {
  Backend Backend
}

func (c *Conn) Query() ([]byte, error) {
  return c.Backend.Query()
}

type Backend interface {
  Query() ([]byte, error)
}

type SQLBackend struct {}

func (db *SQLBackend) Query() ([]byte, error) {
  ...
}

type NoSQLBackend struct {}

func (db *NoSQLBackend) Query() ([]byte, error) {
  ...
}

And that’s all there is to it.

For more design pattern examples, please checkout rolandjitsu/go-design-patterns.