Let’s assume you are trying to build a new API for a CLI. In this case, we are going to build a limited size console.
On our console, we are going to have a buffer of runes that we can add, remove, or consult at any time.
So, let’s see how this will be built:
type Buffer struct {
width, height int
buffer []rune
}
func NewBuffer(width int, height int) *Buffer {
return &Buffer{width, height, make([]rune, width*height)}
}
func (b *Buffer) At(index int) rune {
return b.buffer[index]
}
To keep the example short, we are only going to add “define the At method,” which will consult the character (or rune) at the ‘index’ index.
We now have a Buffer for our console and a constructor. This would work for everyone; however, we should give the user a less complex interface to work with as they use our console. The user will be able to handle the Buffers if they want to, but we are going to handle them with an interface that configures the structures for them.
So, let’s give them a viewport to handle that buffer:
type Viewport struct {
buffer *Buffer
offset int
}
func NewViewport(buffer *Buffer) *Viewport {
return &Viewport{buffer: buffer}
}
func (v *Viewport) GetCharaterAt(index int) rune {
return v.buffer.At(v.offset + index)
}
Right now, the user can interact with a portion of the buffer without needing to get it to its totality, as some buffers can get too big to handle.
Again, we are letting the user interact with the viewport, if they want, by using its constructor and methods.
Finally, we can add the so-called “Façade” to our program:
type Console struct {
buffers []*Buffer
viewports []*Viewport
offset int
}
func NewConsole() *Console {
b := NewBuffer(200, 150)
v := NewViewport(b)
return &Console{[]*Buffer{b}, []*Viewport{v}, 0}
}
func (c *Console) GetCharaterAt(index int) rune {
return c.viewports[0].GetCharaterAt(index)
}
On our console, we can have as many buffers and viewports as we want. Most systems are initialized with just a buffer or a viewport; however, this can easily be handled by adding another constructor in which we can specify how many and of what we want, and then add them to the buffer and viewport variables inside our Console instance.
func main() {
c := NewConsole()
u := c.GetCharaterAt(1)
fmt.Println(u)
}
The user only has to initialize a console with its constructor without having to think about the complexity behind the program; thus, the façade works as intended.
package mainimport "fmt"type Buffer struct {width, height intbuffer []rune}func NewBuffer(width int, height int) *Buffer {return &Buffer{width, height, make([]rune, width*height)}}func (b *Buffer) At(index int) rune {return b.buffer[index]}type Viewport struct {buffer *Bufferoffset int}func NewViewport(buffer *Buffer) *Viewport {return &Viewport{buffer: buffer}}func (v *Viewport) GetCharaterAt(index int) rune {return v.buffer.At(v.offset + index)}type Console struct {buffers []*Bufferviewports []*Viewportoffset int}func NewConsole() *Console {b := NewBuffer(200, 150)v := NewViewport(b)return &Console{[]*Buffer{b}, []*Viewport{v}, 0}}func (c *Console) GetCharaterAt(index int) rune {return c.viewports[0].GetCharaterAt(index)}func main() {c := NewConsole()u := c.GetCharaterAt(1)fmt.Println(u)}
Check out the five other design patterns in GO:
Free Resources