Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

What the Singleton Pattern Costs You

DZone's Guide to

What the Singleton Pattern Costs You

Singletons increase coupling, lower testability, and reduce your ability to reason about the code. All of this works towards creating unbreakable assumptions about it.

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

Do you use the singleton pattern? If not, I’m assuming that you either don’t know what it is or that you deliberately avoid it. If you do use it, you’re probably sick of people judging you for it. The pattern is something of a lightning rod in the world of object-oriented programming.

You can always use Stack Overflow as a litmus test for programming controversy. Someone asked “what was so bad” about singletons, and voting, responding, and commenting went off the charts. Most of those responses fell into the negative category. Outside of Stack Overflow, people call singletons evil, pathological liars, and anti-patterns. People really seem to hate this design pattern — at least some of them do, anyway.

NDepend takes a stance on the singleton pattern as well, both in its rules and on the blog. Specifically, it encourages you to avoid it.

But I’m not going to take a stance today, exactly. I understand the cathartic temptation to call something evil in the world of programming. If some (anti) pattern, framework, or language approach has caused you monumental pain in the past, you come to view it as the tool of the devil. I’ve experienced this and turned it into a blog post, myself.

Instead of going that route here, however, I’m going to speak instead about what it can cost you when you decide to use the singleton pattern — what it can cost the future you, that is. I have a consulting practice assessing third-party codebases, so I’ve learned to view patterns and practices through a dispassionate lens. Everything is just trade-offs.

What Is the Singleton Pattern, Anyway?

Before I go any further, I should probably explain briefly what this thing is, in case you aren’t familiar. I won’t belabor the point, but here’s a quick example. (I’m keeping this to a bare minimum and not worrying about non-core concepts like thread safety.)

public class ASingleton
{
    private static ASingleton _instance;

    private ASingleton() { }

    public static ASingleton Instance
    {
        get
        {
            if (_instance == null)
                _instance = new ASingleton();

            return _instance;
        }
    }
}


Alright, so why do this? Well, the pattern authors define its charter as serving two principal purposes:

  • Ensure that only one instance of the class ever exists.
  • Provide global access to that single instance.

So let’s consider implications and usage. When you use this pattern, you define an object that will exist across all application scope and that you can easily access from anywhere in the code at any time. A logger is, perhaps, the most iconic example of a singleton use case. You need to manage access to a resource (file), you only want one to exist, and you’ll need to use it in a lot of places. A marriage made in heaven, right?

Well, in principle, yes, I suppose so. But in practice, things tend to get a lot messier. Let’s take a look at what can happen when you introduce the singleton pattern into your codebase. Let’s look at what it can cost you.

Side Effects Hurt the Ability to Reason About Your Code

Think for a moment about how you would invoke an instance method on a singleton class. For instance, let’s say that you’d implemented your own logger. Using it might look something like this:

public Order BuildSimpleOrder(int orderId)
{
    var order = new Order() { Id = orderId };

    Logger.Instance.Log($"Built order {orderId}");

    return order;
}


The method constructs an order in memory based on the order’s ID and then returns that order, stopping along the way to log. It seems simple enough, but the invocation of the logging presents a simple conundrum. If I’m looking at just the signature of BuildSimpleOrder, say, via IntelliSense, the logging is utterly opaque to me. It’s a hidden side effect.

When I instantiate the class containing BuildSimpleOrder, I don’t inject a reference of a logger into a constructor. I don’t pass the logger in via a setter, and I don’t give it to this method as a parameter. Instead, the method quietly reaches out into the ether and invokes the logger which, in turn, triggers file I/O (presumably).

When you have singletons in your codebase, you lose any insight into what methods are doing. You can take nothing for granted, and you must inspect each and every line of your code to understand its behavior, making it generally harder to reason about. This was why Misko Hevery called singletons “liars.” BuildSimpleOrder now does something completely unrelated to building a simple order — it writes to a file.

You Give Up Testability

Consuming singletons doesn’t just make your code harder to reason about. It also makes your code harder to unit test. Think of writing unit tests for the method above. Without the logger instance, that would be a piece of cake. It’d look like this:

[TestMethod]
public void BuildSimpleOrder_Sets_OrderId_To_Passed_In_Value()
{
    const int id = 8;
    var processor = new OrderProcessor();

    var order = processor.BuildSimpleOrder(id);

    Assert.AreEqual<int>(id, order.Id);
}


Then, if building the order demanded a bit more complexity, you could easily add more tests. This is an imminently testable method…without the call to the logger.

With the call to the logger, however, things get ugly. That call starts to trigger file I/O, which will give the test runner fits. Depending on the runner and machine in question, it might throw exceptions because it lacks permissions to write the file. It may actually write the file and just take a while. It may throw exceptions because the file already exists. Who knows? And worst of all, it may behave entirely different on your machine, on Bob in the next cubicle’s machine, and on the build.

Normally when you have external dependencies like this, you can use dependency injection and mock them. But singletons don’t work that way. They’re inline calls that you, as a consumer, are powerless to interpose on.

You Incur Incredible Fan-In and Promote Hidden Coupling

Singletons tend to invite use and to invite abuse. You’d be surprised how handy it can be to have a deus ex machina at your disposal when you suddenly need two objects at the complete opposite ends of your object graph to collaborate.

Man, we could solve this really quickly and with little rework if the login screen could just talk directly to the admin screen. Hey, I know! They both have access to the logger. We could just add a flag on the logger. It’d only be temporary, of course…

Yeah, we both know that flag will NOT be temporary. And this sort of thing happens in codebases that rely on the singleton pattern. More and more classes take direct dependencies on the singletons, creating a high degree of fan-in (afferent coupling) for the singleton types. This makes them simultaneously the riskiest types to change, the hardest types to test, and the most likely types to change, all of which adds up to maintenance headaches.

But it even gets worse. Singletons don’t just invite types to couple themselves to the singleton. They also invite types to couple their own logic through the singleton. Think of the login and admin screens communicating through the singleton. It acts as a vessel for facilitating hidden couplings as well.

And while I will concede that use of the pattern does not automatically constitute abuse, I will say that inviting it is problematic. This holds doubly true when your only method of preventing abuse is vigilant, manual oversight of the entire codebase.

You Lose the Ability to Revisit Your Assumptions

I’ll close by offering a more philosophical cost of the singleton pattern that builds on the last three. You have code with side effects that you have a hard time reasoning about. Likewise, you can’t easily test it, and you create an outsize dependency on the singleton pattern instances. What does all of that wind-up mean?

It means that changing your assumptions about the singleton itself becomes nearly impossible. These things cast themselves in iron in your codebase and insert tentacles into every area of your code. Imagine the horror if a business analyst came along and said something like, “Why can’t we just have two simultaneous log files?” or, “Why can’t we have sessions that stop and then restart later?”

In answer to these seemingly reasonable requests, the architect will turn red and splutter, “Are you CRAZY?! Do you know how much rework that would mean?” When you use singletons, you had better make sure that there could really only ever be one log file, print spooler, session, etc. and that your instance of that will last the same length as the program. Because if that assumption ever needs to change, you’ll find yourself in a world of pain, to the tune of a total rewrite.

I won’t call the singleton pattern evil or rail against it. But I will tell you that it extracts a pretty high toll from you in the long run.

Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:
singleton pattern ,coupling ,design patterns ,java ,tutorial

Published at DZone with permission of Erik Dietrich, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}