Singleton

Ensure a class only has one instance, and provide a global point of access to it.

Participants

RoleIn this exampleResponsibility
SingletonPaletteHolds the one shared palette. Ruby's Singleton module makes .new private and adds .instance.
ClientPalette.instanceReaches the shared instance through the class.

Ruby implementation

singleton.rb
require "singleton"

# The game's colour palette. There is exactly one, and every part of the
# UI should see the same colours. Ruby's standard library ships the pattern
# as a module: including it makes .new private and adds .instance.
class Palette
  include Singleton

  def initialize
    @colours = { grass: "#3b6b35", path: "#b8925a", goblin: "#7cc35b" }
  end

  def [](name)
    @colours.fetch(name)
  end

  def []=(name, colour)
    @colours[name] = colour
  end
end

In use

Palette.instance[:goblin]  # => "#7cc35b"
Palette.instance[:grass] = "#2f5a2a"

Palette.instance.equal?(Palette.instance) # => true
Palette.new # => NoMethodError: private method 'new' called

Notes

Ruby idioms

  • require "singleton" and include Singleton is the standard library version. It’s thread-safe on first access and also blocks dup and clone.
  • A module with state works too. module Palette; def self.colours = (@colours ||= {}); end is a common, lighter alternative.
  • Prefer passing the instance in. MapView.new(palette: Palette.instance) keeps the dependency visible and lets tests pass something else.

Reach for it when

  • There must be exactly one of something by nature: the terminal the game draws to, a process-wide registry, a log sink.

Watch for

  • It’s global state. Every test shares it, and a change in one place is visible everywhere. The tests for this pattern show that. Be wary of using a singleton just to avoid passing an argument.

Use a singleton when there must be exactly one of something, and pass the instance in where you can so it stays easy to test.