Design Patterns in Go: Flyweight, or go easy on your memory

Introduction

The flyweight pattern is a pattern that helps minimize memory usage by sharing and reusing data. A common example is wordprocessor where each character not only holds the character like ‘A’ but also things like markup and formatting data. Instead of having each element of a document carry all this data, this markup and formatting data is stored using flyweight patterns, to which the elements refer. This method saves a lot of memory.

So, flyweight objects can have:

  • Intrinsic state: invariant and shareable, but also context independent (like aformentioned character ‘A’)
  • Extrinsic state: that could be the position of the character in the document.

So, what does it look like? Well, a very simple version is this:

Implementation in Go

Open a commandline or terminal in an empty directory and type:

go mod init flyweight_pattern

Now open your favourite IDE and add a main.go file.

Start with the following:

package main

import (
	"fmt"
)

The flyweight interface

Now we will define the Flyweight interface:

type Character interface {
	Display(row, col int) // extrinsic state passed as parameters
}

This code defines the Character interface which defines a contract for displayomh characters at specific positions. The Display() method takes row and column parameter, which represents the extrinisic state. This state will be passed in from external context rather than stored within the flyweight objects themselves.

The concrete implementation

The ConcreteCharacter struct is our actual flyweight implementation, as it contains only the symbol field as its intrinsic state. Why only the symbol? By storing the unchanging symbol, and excluding extrinsic data like the row and column multiple references can safely share the same instance. The Display() method combines extrinsic and intrinisc state to produce the desired output:

type ConcreteCharacter struct {
	symbol rune
}

func (c *ConcreteCharacter) Display(row, col int) {
	fmt.Printf("Character '%c' at position (%d, %d)\n", c.symbol, row, col)
}

The flyweight factory

Now we come to the CharacterFactory which will act as the central management system for the flyweight instances, maintaining a map associating each symbol with its corresponding flyweight object. The GetCharacter() method implements the core flyweight logic. It first checks if a flyweight object for the requested symbol has already been created. If it does it returns the this object, if not it stores this object it creates and stores one, and returns it:

type CharacterFactory struct {
	characters map[rune]*ConcreteCharacter
}

func NewCharacterFactory() *CharacterFactory {
	return &CharacterFactory{
		characters: make(map[rune]*ConcreteCharacter),
	}
}

func (f *CharacterFactory) GetCharacter(symbol rune) *ConcreteCharacter {
	if char, exists := f.characters[symbol]; exists {
		fmt.Printf("Reusing existing flyweight for '%c'\n", symbol)
		return char
	}

	fmt.Printf("Creating new flyweight for '%c'\n", symbol)
	char := &ConcreteCharacter{symbol: symbol}
	f.characters[symbol] = char
	return char
}

func (f *CharacterFactory) GetCreatedFlyweightsCount() int {
	return len(f.characters)
}

Testing time

Now we can test our setup:

func main() {
	factory := NewCharacterFactory()


	char1 := factory.GetCharacter('A')
	char2 := factory.GetCharacter('B')
	char3 := factory.GetCharacter('A') // Reuses existing flyweight
	char4 := factory.GetCharacter('A') // Reuses existing flyweight


	char1.Display(1, 1)
	char2.Display(1, 2)
	char3.Display(2, 1) 
	char4.Display(3, 1) 

	fmt.Printf("\nTotal flyweights created: %d\n", factory.GetCreatedFlyweightsCount())
	fmt.Printf("Total character references: 4\n")
	fmt.Printf("Memory saved by sharing flyweights!\n")
}

This function demonstrates the pattern’s effectiveness: it creates four character references but it generates only two flyweight objects. As you can see, it displays the characters at different positions using the extrinisic state.

Conclusion

The Flyweight pattern is all about working smarter, not harder. Instead of creating a mountain of identical objects, you reuse what you already have and just plug in the bits that change. In our Go example, we only made two flyweights but displayed four characters — not bad for a few lines of code!

It’s the kind of pattern that quietly does its job behind the scenes, saving memory and keeping things snappy. Whether you’re building a game, a text editor, or just like writing efficient code, the Flyweight pattern is a neat trick to keep in your developer toolbox.

  1. Make the flyweightfactory threadsafe.
  2. Use generics to make the whole pattern more flexible.

The Code Nomad
The Code Nomad
Articles: 167

Leave a Reply

Your email address will not be published. Required fields are marked *