Here’s another freebie from the deep RubyTapas stacks. This one is about a truth of object modeling that we don’t often talk about: not every object needs to have state. If an object has no state, there’s no need to have more than one of it. And for stateless objects, having a class just to generate a single instance may be superfluous!
Director’s commentary: This one first aired as episode #13 in October 2012. In retrospect I’d tighten up the pacing on. And I hate that the ending monologue on the Singleton pattern has no illustration on the screen. Also, I really hope I don’t sound that bored in more recent videos.
While I was at Ruby DCamp a few weeks ago Sandi Metz asked me to write a version of Conway’s Game of Life in a semi-functional, stateless style. I decided to represent the concept of a “live cell” and a “dead cell” as two different kinds of object. The game grid would then be represented by a simple array of arrays, populated by live cells and dead cells.
I was all set to write a class for each…
class LiveCell # ... end class DeadCell # ... end
…when it occurred to me: since this is a stateless implementation, there was no real need to have individual
LiveCell objects for every live cell on the board. And the same goes for dead cells. Without any state, every instance would be exactly the same. And since the objects didn’t have any need for initialization, why even bother with classes?
Instead, I created the objects as singleton instances of the
<a href="https://www.rubytapas.com/out/ruby--object">Object</a> class. I assigned the instances to constants, so they would be available anywhere in the program. Then I used Ruby’s singleton class syntax to add methods to each one. Each object needed two methods: a
<a href="https://www.rubytapas.com/out/ruby--object-to_s">#to_s</a> method to represent the cell on an ASCII grid, and a method to determine what the next generation of its grid square would contain: either a live cell or a dead cell.
LIVE_CELL = Object.new class << LIVE_CELL def to_s() 'o' end def next_generation(x, y, board) case board.neighbors(x,y).count(LIVE_CELL) when 2..3 then self else DEAD_CELL end end end DEAD_CELL = Object.new class << DEAD_CELL def to_s() '.' end def next_generation(x, y, board) case board.neighbors(x,y).count(LIVE_CELL) when 3 then LIVE_CELL else self end end end
Then I could just populate my grid with the same LIVE_CELL and DEAD_CELL objects, over and over again.
[ [DEAD_CELL, LIVE_CELL, LIVE_CELL, DEAD_CELL], # ... ]
This worked quite well, but I didn’t like the fact that the singleton objects had to be created in two steps. So I decided to see if I could create the object, assign it to a constant, and define methods on it, all in a single statement.
To accomplish this feat, I took advantage of the fact that Ruby allows variables and constants to be assigned inside parenthesized sub-expressions of a statement.
class << (LIVE_CELL = Object.new) def to_s() 'o' end def next_generation(x, y, board) case board.neighbors(x,y).count(LIVE_CELL) when 2..3 then self else DEAD_CELL end end end class << (DEAD_CELL = Object.new) def to_s() '.' end def next_generation(x, y, board) case board.neighbors(x,y).count(LIVE_CELL) when 3 then LIVE_CELL else self end end end
The resulting syntax was a bit obscure, but it accomplished my purpose succinctly.
A class plays two roles in an OO program:
- It provides a container for behavior that’s shared by many objects.
- It acts as an object factory, manufacturing new instances and
ensuring they are initialized correctly.
When we have an object which does not need to share behavior with any others objects, and which requires no initialization, that renders both roles of a class superfluous. In cases like this, it can make more sense to just use a one-off singleton object.
Another way to accomplish this is to use a module as our singleton object, and only define class-level methods on the module. Let’s update the example to use this approach instead.
module LiveCell def self.to_s() 'o' end def self.next_generation(x, y, board) case board.neighbors(x,y).count(LiveCell) when 2..3 then self else DeadCell end end end module DeadCell def self.to_s() '.' end def self.next_generation(x, y, board) case board.neighbors(x,y).count(LiveCell) when 3 then LiveCell else self end end end [ [DeadCell, LiveCell, LiveCell, DeadCell], # ... ]
This is a perfectly legitimate way to implement singleton objects. Personally, I feel that using a singleton instance of
<a href="https://www.rubytapas.com/out/ruby--object">Object</a> is a little more intention-revealing, since modules are usually used as namespaces and as a means of sharing behavior. But I don’t feel that strongly about it. The main thing I hope to convey in this episode is simply that sometimes a singleton object is all we need, and when that’s all we need, that’s all we should use.
I should make one last point here: I’m talking about singleton objects, not the Singleton Pattern. The Singleton Pattern describes a way to control the construction of a particular class such that only one instance is ever constructed. Singletons following this pattern have often been used to manage a great deal of centralized state, a situation that can lead to a number of problems, including thread contention and proliferation of dependencies. The singleton objects we’ve defined here are stateless and more akin to the immutable singletons that Ruby provides like
nil. As such they are somewhat less prone to abuse.
Okay, enough for today. Happy hacking!