Singleton
Ensure a class only has one instance, and provide a global point of access to it.
Participants
| Role | In this example | Responsibility |
|---|---|---|
| Singleton | Palette | Holds the one shared palette. Ruby's Singleton module makes .new private and adds .instance. |
| Client | Palette.instance | Reaches the shared instance through the class. |
Ruby implementation
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
endIn 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"andinclude Singletonis the standard library version. It’s thread-safe on first access and also blocksdupandclone.- A module with state works too.
module Palette; def self.colours = (@colours ||= {}); endis 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.