Pseudo Associative

Random thoughts on software development

1 note

The Misunderstood Singleton

Singleton classes are often used, but in secrecy. Nobody wants to admit they used them because the purists have decided they are pure evil. I understand the rational used the wrong way it makes code difficult to maintain and test. My issue with this is that don’t like dogmas. The idea that there is only the right way and the wrong way.

But that is just silly. As with any technology or paradigm, the answer is always “It depends”. Software developers should not be told “don’t use singletons”. They should be told what kind of problems singletons can cause and how to deal with them.

Before we look at the problem, lets briefly take a step back and talk about what they can be used for.

The Purpose of a Singleton

In any computer system or program there are shared resources. There is only one screen, one main memory, usually just one network connection etc. For a database program there is usually just one database connection. A singleton is thus suitable to represent these types of resources.

Where we get Into Trouble, Testing

Your singleton might represent a connection to a server processing credit cards. When testing part of the functionality interacting with this singleton we don’t want to accidentally charge our credit card in our test-code. Or lets take the example of game rendering sprites on screen. Let’s say we want to create tests for the Go language code below:

type Sprite struct {
    image Image
    position Point
}

func (sprite *Sprite) draw() {
    var screen Screen = graphics.SharedScreen()
    screen.drawImage(sprite.image, sprite.position)
}

If we want to test our screen drawing that is difficult because we have a hardcoded dependency on the screen singleton object.

Dependency Injection

But the problem is easily solved by making the screen an argument.

func (sprite Sprite) draw(screen Screen) {
    screen.drawImage(sprite.image, sprite.position)
}

func gameloop() {
    screen = graphics.SharedScreen()
    var player Sprite = getPlayer()
    player.draw(screen)
}

Or alternatively we can make the screen object used for a sprite part of it.

type Sprite struct {
    screen Screen
    image Image
    position Point
}

func (sprite *Sprite) draw() {
    var screen Screen = sprite.screen
    screen.drawImage(sprite.image, sprite.position)
}

Then it is easy to replace the screen the sprite is drawn to with a mock object, where we can record and test drawing commands.

var mockScreen *MockScreen

func TestSpriteDrawing(t *testing.T) {
    var sprite Sprite = createTestSprite()
    sprite.screen = mockScreen
    sprite.draw()
}

So my point is that singletons are not really a problem if you pass them around as arguments rather than writing SharedScreen() every time some code needs to use the Screen. The problem is the typical usage of singletons not the fact that you have a type (class) of which there can only be one instance.

Final Word

And finally, the problems singleton classes can cause largely depends on where your software layer it is located. If a low level function being used by a lot of code uses a singleton then most of your code ends up depending on a singleton. That means almost any code you want to test needs to somehow mock or replace this singleton. That is bad.

The problem of singletons increase as you go down the layers of your software. My personal rule is that if you have singletons try to limit them to the top most layer. I generally prioritize to test my lower layers, because if something breaks in the lower layer it affects everything above. Errors in the top layer will usually cause more local problems.

Filed under design patterns golang singleton

  1. assoc posted this