神刀安全网

6 Reasons Why You Should Avoid Singletons

A little more than a decade ago, I used singletons a lot. I had a shortcut in eclipse where I only had to write "singleton" and press "CTRL+Space", and boom: Eclipse would generate all the necessary code to make the currently open class be a singleton. Since then, I learned a lot. And the tools we have to avoid Singletons (i.e. the Singleton Pattern) have matured. Now I try to avoid the singleton pattern like the plague. Here is why…

Yes, I am really writing a blog about why the "Singleton Pattern" is bad. In 2016. But it comes up in discussions time and again, so here is a list of reasons why you should avoid singletons.

Actually, there’s nothing wrong with having only one instance of an object. But using the "Singleton Pattern" to achieve this is never the best solution. Towards the end of this post, I will show you why and how you can do it better.

Some of the reasons why using the "Singleton Pattern" is bad are closely related to others. Like, "Inversion of Control Violation" has mostly the same consequences as "Coupling to Concrete Implementation". But I will list them separately anyway because they give you different motivations to avoid the Singleton Pattern.

The Singleton Pattern

The singleton pattern is a design pattern that you can use when you need exactly one instance of an object. An implementation looks like this:

public class InterestingThingsDoer {     private static InterestingThingsDoer instance = new InterestingThingsDoer();      //possible some instance state      private InterestingThingsDoer() {}      public static InterestingThingsDoer getInstance() {         return instance;     }      public ResultType doSomethingInteresting() {         //something interesting     } }

Callers can now get the single instance of the Singleton and make it perform some work for them:

public void evenMoreInteresting() {     InterestingThingsDoer doer = InterestingThingsDoer.getInstance();     ResultType result = doer.doSomethingInteresting();     //do something even more interesting... }

Great – Now you can implement your abstract factories, services, facades, … in a way that guarantees that every caller operates on the same, shared instance. What could possibly go wrong?

Coupling to Concrete Implementation

Singletons couple all callers to the concrete implementation of the singleton, because the callee asks for the singleton object. Your classes become harder to test: You cannot test them in isolation, without also running code from the Singleton.

In the example from above, you cannot test the method "evenMoreInteresting" without also running some code from "InterestingThingsDoer". You cannot test this method in isolation.

Why is this a problem?

Suppose the method from above looked like this:

public void evenMoreInteresting() {     InterestingThingsDoer doer = InterestingThingsDoer.getInstance();     ResultType result = doer.doSomethingInteresting();     ResultTypeDao dao = ResultTypeDao.getInstance();     dao.save(result); }

Now you need a database to test "evenMoreInteresting()". And if "evenMoreInteresting()" is again defined in a singleton (e.g. because it’s part of a stateless service, and everyone knows that you only need one instance of a stateless service), you’ll need a database to test every method that calls "evenMoreInteresting()". And so on. A large percentage of your tests will be integrated tests, andthose are evil.

Inversion of Control Violation

When you use a singleton in one of your classes, you violate "Inversion of Control" : Your class does not get the collaborator (the singleton object) injected, it usually asks for the singleton object.

Why is this a problem?

In the code above, evenMoreInteresting() has complete control over the control flow, because it gets a concrete implementation of all required collaborators and decides what to do with them.

To achieve inversion of control, you have to make sure that classes are not responsible for finding / creating their collaborators (among other things). You do this with a technique called "dependency injection" – Preferably "constructor injection".

But my constructor signatures are getting too big!

If you change some existing code to dependency injection, especially constructor injection, you will notice that some of your constructors have lots and lots of parameters. Take that a chance for refactoring: This class is clearly doing too much!

Open / Closed Principle Violation

Singletons themselves violate the "Open / Closed Principle" : If you want to extend or change the behavior of the singleton, you have to change the class. In other words, you cannot change the behavior of "InterestingThingsDoer" from above without opening the file and editing the code.

Why is this a problem?

Changing code is much riskier than adding new code. When you change existing code, there is always the possibility that you’ll introduce undesired side effects (a.k.a "defects" or "bugs") in totally different areas of the code that depend on the changed code.

When you can extend the functionality of existing code by just adding new code, the probability that you’ll introduce side effects in "unrelated" areas of the code goes way down (but might still be greater than zero, depending on your architecture and design).

So, classes / modules / functions that violate the open / closed principle increase the probability that you’ll introduce defects later, when you need to change them.

Single Responsibility Principle Violation

Singletons themselves violate the "Single Responsibility Principle" : In addition to their main responsibility, they are also responsible for implementing their own lifecycle.

Classes that use a singleton also violate the "Single Responsibility Principle": In addition to their main responsibility, they are also responsible for deciding the concrete class of (at least one of) their collaborators.

Why is this a problem?

Every responsibility of a class is also a reason to change. You’ll have to change "InterestingThingsDoer" whenever you need to change what it does and whenever it’s lifecycle changes. And you have to change all callers of "InterestingThingsDoer" when its lifecycle changes, since they have multiple responsibilities too.

And, as already stated above, changing code is inherently risky: If you change code because some responsibility of a class has changed, there is some risk that you’ll also break code that deals with other responsibilities of the same class.

Also, if your class has multiple responsibilities, you have to test all of them. And you probably cannot test them in isolation. This means you’ll have to write more complicated / hard to understand tests.

Dependency Inversion Principle Violation

The "Dependency Inversion Principle states that the high-level policy of your system should not depend on low-level details. Also, abstractions should never depend on details.

Singletons often implement some low-level detail. When you use them from your business logic (the high-level policy of your system), you violate the "Dependency Inversion Principle".

Why is this a problem?

The dependency inversion principle ensures that you can change the low-level details of a system without having to change the high-level "business logic".

Imagine a vending machine: You select a product, pay the price, get the product. When the implementation of this high-level workflow has any dependencies to low-level details, like how the payment works, you might have to change code dealing with this high-level workflow when you add some new payment method (like NFC payment). You don’t want this, because changing code is dangerous.

Increased Coupling / Decreased Cohesion

Often, multiple classes depend on the concrete implementation of a singleton. This increases coupling in your system: When you want to change the behavior of the singleton, you probably have to check and change all the callers. You always have common coupling between all classes that use the singleton : They share a global variable – the singleton!

Sometimes, moving code to singletons (to minimize code duplication, for example) can also decrease cohesion: You move stuff to different classes / packages that conceptually should be a part of the original class / package.

Why is this a problem?

In a tightly coupled system, changes ripple through the system: You want to change one tiny bit of functionality, but to do that, you’ll have to modify 20 classes and 50 tests.

In a system with low cohesion, it is hard to even find out where to make a change: It can be any one of 10 different modules. Or it could be all of them. Sometimes it’s even hard to know if you have found all the places you have to change to fix one tiny defect.

One and Only One Object

If you really want only one instance of a certain class, implement inversion of control for your dependencies. If you are already using a dependency injection container (like Guice or Spring ), just make sure all your singleton objects are managed by that container. All those containers have a way to declare objects as singleton.

If you don’t want to use one of those containers, just create all instances of objects you’ll need to be singleton in one place, and inject them to other objects via their constructor. You may only need one instance throughout the lifetime of your application, but you don’t need the Singleton pattern for that. Just construct one, and inject it into your classes as desired.

And if you don’t have inversion of control for your dependencies right now, start using it. It will pay off.

Stateful vs. Stateless Singletons

Do you remember the "//possible some instance state" in "InterestingThingsDoer" from above? There are basically two types of singletons:

  • Stateless singletons: You don’t need more than one instance of this class, because it has no state. But you could have many of them: It just does not matter. Because they have no state :)
  • Stateful singletons: You must have exactly one instance of this class, because it represents some globally shared state.

The first category is not a problem. Just use one of the "sane" approaches for creating singletons from above, don’t use the "Singleton Pattern". And even if you construct more than one, it’s probably not a problem: The class is stateless anyway, you’ll just end up consuming more resources. This overhead is probably negligible for most applications (but not all!).

The second category is probably even a problem when you use one of the "sane" approaches to make sure there is only one object: Shared, mutable state makes it easy to introduce defects when changing stuff and makes it hard to scale applications.

Thanks to Samir Talwar who convinced me to write this blog post. He also helped me review the very first draft of this post.
Want to read more like this? Never miss one of my articles: Readers of my newsletter get my articles before anyone else.Subscribe here!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 6 Reasons Why You Should Avoid Singletons

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮