Singleton corresponds to a software design pattern. Design patterns, as I discussed in my article on Strategy, are nothing more than a catalog of solutions to common problems. Each design pattern has pros and cons; implementation benefits and costs. In the specific case of Singleton, critics consider these “costs” too great.
In this article, I will explain what a singleton is, what “problems” it can bring to the code, and what are the alternatives for its use. Continue reading! What is a Singleton?
To understand the difficulties that the Singleton design pattern brings, we first need to define what a “Singleton” is. After all, it is often the very confusion about the concept that makes some people against this standard. We can start with the following statement: a single instance of a given object during the lifetime of a request is not a Singleton. A Singleton, by definition, is precisely this globally accessible instance in the project.
Analogous to the descriptions given by GoF (Gang of Four), in the book “Design Patterns”, we can list 3 basic characteristics to define what we need to create a Singleton: It must have a single instance during the lifetime of the application; It should not be possible to instantiate it through its constructor, which needs to have preferably private visibility; Your instance must be available globally, in your project.
These definitions are very important, as failure to understand what a Singleton represents can end up leading people to not understand the accusation of it being considered an anti-pattern.
Anti-Pattern? Is it to eat?
Anti-Patterns, contrary to what it sounds at first glance, are not necessarily the opposite of a design pattern. So-called “anti-patterns”, in short, are common responses to frequent problems, which are usually ineffective and have a high risk of being counterproductive.
Attention to detail: these are “common answers” to “common problems”. In other words, being an anti-pattern does not nullify the fact that Singleton continues to be a design pattern. Anti-patterns have more to do with the wrong use of a correct solution, than the solution itself being something bad ─ although that can also happen.
The Singleton in practice The Singleton, as already explained, provides the developer with a single instance of an object with a global scope. Below is an example of what its implementation would look like using the Java programming language.
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } Note: Although the name “Singleton” is explicit in the example class, it is important to emphasize that you do not need to mention the name of the design pattern in the class name. This was just done to illustrate its structure.
Singleton can be a viable solution if you are in a situation where these two problems arise: You need only one instance of an “object X”; You need to access this instance from anywhere in your application.
If one of the two items above are not part of your needs, there is probably another way out of your problem.
If you don't need a single instance, your object's constructor doesn't need to be private. Just instantiate your object wherever you need to use it; If you don't need to access your instance from anywhere, it means you have a limited scope. In this case, an alternative would be to identify “from where” it is necessary to access your object.
One way to do this is to create a private static field that stores your instance and pass this object through dependency injection wherever it is used. The topic “dependency injection” and its connection with Singleton will be discussed later in this article.
Benefits of Singleton Despite some people's resistance, Singletons have very interesting benefits. Check out some of them: It is extremely easy to access: serving as a “global scope” variable, it is clear that its access is exceptionally simple. There is no need to transport your object instance throughout your system.
Guarantees that there is only a single instance: regardless of the object or reason, Singleton will guarantee that there is only a single instance within your project. This way, logical flaws that may involve this rule are avoided.
Something interesting to comment on, which has a direct connection with the benefits mentioned above, is its versatility to act as a Mutex (Mutual Exclusion) key. In multi-threaded environments, where two or more operations can compete, the “key” instance remains one. This way, execution control is made easier. Of course, Singleton itself is not free from competition. Therefore, it needs appropriate treatment depending on the language in which it is being used, so that it can be considered a thread-safe solution.
If you have never heard of Mutex (also called “semaphores” or “locks”), but are interested in the subject, I recommend reading this article.
Singleton Costs By doing a brief search, we can find several articles by different authors commenting on different costs involved in implementing a Singleton. Among the most discussed, the ones that I consider most interesting to address, are: Breakage of SOLID principles; “Lack of traceability” factor; Difficulty implementing tests; Sacrifices transparency for convenience.
To make it easier to digest what each problem represents, I will explore each of these factors in the following topics. It breaks SOLID principles The first and most common issue when it comes to Singleton is that it breaks SOLID principles. To be more exact, the Single Responsibility Principle (SRP).
The following snippet is responsible for the problem: public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } This happens because, in addition to the Singleton having control of the object's life cycle (its internal instance), it also guarantees its access. With these two factors added together, we end up with two responsibilities.
But, what should be done to prevent the SRP from breaking? In short, the responsibility for populating the instance should be delegated to another class; exactly one of the proposals of another design standard, known as Monostate.
If you want to know more about SRP and understand why it is important, I recommend reading our article Discover one of the most important SOLID principles: Single Responsibility Principle, written by my colleague, Pacifique Mukuna.
Lack of traceability Suppose you are in a situation where you need to create and control user instances of a given system, and that only one user can be logged in at a time. One possibility would be to create a “manager” class for this connected user instance.
There are some possible alternatives to continue with the development. Creating a Singleton serving as the “management class” is one of them. Therefore, it would not be necessary to worry about the parts of the system that will need to consume this connected user: just retrieve the instance of the management class, which is accessible from anywhere in the project.
Now, we can reflect: “in this system, which object can change the logged in user?” It seems easy: “the Manager” would probably be the first answer.
Reflecting a little more on the solution, we can observe a very important detail about this design: the management class is accessible from anywhere in the system. So the solution implies that the user can be changed from any location as well.
Going a little further in this exercise, we can forget the “user” class and replace it with “Generic Object X”, which is constantly modified by the objects that call it.
Note that as our Manager object is used, the “lack of traceability” factor becomes more common.
The issue here is more philosophical than practical. The fact is that, no matter how justified its use, we can conclude that there are two absolute and intrinsic issues in this design pattern: You cannot guarantee that the properties of your object will not change, when they should not change. In this regard, global access is the factor that makes it very difficult to predict the misuse of Singleton; As a consequence of the previous factor, if there is an undue change in the properties of your object, it is extremely complex to identify the point at which the change is taking place. Mainly in large applications where operations that modify object properties are common.
Makes it difficult to implement tests On this point, I want you to pay attention. I'm not saying that Singleton is difficult to test, but rather that it makes implementing tests difficult. This generally occurs in the code that consumes it.
In order not to expand on this explanation, take into account that to understand why Singleton makes test implementation difficult, it is necessary to have a base of what automated tests are. In particular unit tests and how they are implemented.
But, in short, a unit test consists of the following ideas: Testing a class in isolation: if all parts of a given system are working independently, there should be no problem when they are all together. However, this is a debatable subject, and depends on the developer's intention; Test independently: in addition to testing the class in isolation, each test must be absolutely independent of each other. Regardless of execution order, all tests must pass; Test quickly: as a result of the previous points, unit tests have the peculiarity of being small in scope, and, therefore, they are quick to execute.
Unit tests are the basis of what in software engineering is known as the “test pyramid”. In order of priority, they are those that should exist in greater abundance in projects.
To understand the problem that Singleton can present during the execution of a unit test, we can recover the idea described above, about there being a “managing class” for the connected user. Let's call it UserRegistry. Furthermore, let us take into account that a given system has a service with the following check: public class Servico { public boolean usuarioPodeCadastrarNovosClientes() { // Gets the instance of the Singleton RegistroUsuario RegistroUsuario = RegistroUsuario.getInstance(); // Stores the user logged into the system in a variable Usuario usuarioLogado = RegistroUsuario.getUsuarioLogado(); // Makes a return indicating whether the connected user has the permission return usuarioLogado != null && usuarioEhAdmin(usuario)); } private boolean usuarioEhAdmin(Usuario usuario) { return “ADMIN“.equals(usuario.getPermissao()); } } The above service is relatively simple, and only checks whether the user currently logged into the system has permission to register new customers. The method must return “TRUE” if there is a user connected, and this user has “ADMIN” permission.
If you are already used to creating tests, you will easily be able to identify three possible scenarios: either the user is ADMIN; or the user is not ADMIN; or there is no user logged in.
We can create a unit test class to automate these checks, as in the example below: public class ServicoTest { private Usuario usuarioAdmin = new Usuario(“ADMIN“); private User Common user = new User(“COMMON“); private Service service; @Before public void setUp() { this.service = new Service(); } @Test // When there is no user logged in.
public void teste01() { RegistroUsuario.getInstance().setUsuarioLogado(null); Assert.assertFalse(servico.usuarioPodeCadastrarNovosClientes()); } @Test // When a user is logged in, but does not have permissions.
public void teste02() { RegistroUsuario.getInstance().setUsuarioLogado(usuarioComum); Assert.assertFalse(servico.usuarioPodeCadastrarNovosClientes()); } @Test // When a user is logged in and has permission.
public void teste03() { RegistroUsuario.getInstance().setUsuarioLogado(usuarioAdmin); Assert.assertTrue(servico.usuarioPodeCadastrarNovosClientes()); } } It turns out that unit tests, in most cases, run in parallel because they are independent. It is exactly at this point that Singleton becomes a problem.
Execution in parallel causes tests to make modifications to the Singleton in competition, and when test01, for example, asserts information by invoking the service method, it is very likely that another test, such as test02, has already modified the Singleton value again, which would cause a false negative in the test01 assert.
Perhaps the graph below will make it easier to clarify the example described above: Returning to the second item listed about the definition of a unit test: tests must be independent.
Therefore, in some cases, it is extremely difficult to unit test code that consumes a Singleton. Especially if the Singleton has a direct connection with the return of the method you are testing.
Sacrifices transparency for convenience In the article “Singletons are pathological liars” by Misko Hevery, good context is given through testing of what the problem is here: Singletons, among many other problems, can make chain analysis and discovery exceptionally difficult. of dependencies.
The example used by Hevery can be considered somewhat extreme. And perhaps it really is, because it is a very particular case of an event that occurred to him while he was developing for Google.
But, your main point remains valid: the Singleton is nothing more than a global state. Global states make it so that your objects can secretly get hold of things that are not declared in their interfaces. As a result, Singletons turn their interfaces into pathological liars.
Thus, we can interpret the phrase “sacrifices transparency for convenience” as something common with global scope variables, as there are no explicit dependencies in the interface of the code that consumes the Singleton.
Analogous to the image above, we can say that the fazAlgumaCoisa() method, of Object A, does not know that the fazOutraCoisa(), of Object B, uses the Singleton. And again, this makes dependency chain analysis exceptionally complex.
Hevery, in particular, gave a talk for Google Tech Talks in 2008, which I highly recommend if you understand English and are interested in the topic. In the talk, he delves into the fact that he considers Singletons to be a bad practice.
Singleton versus Monostate The Monostate Pattern, or simply “Monostate”, was a design pattern proposed by Robert C. Martin in his article Singleton and Monostate, in 2002, as a “Clean Code” proposal for a Singleton.
This design pattern proposes storing a single instance of an object and providing global access to this instance ─ just like Singleton, however, with some small differences: Its constructor must be public; Your methods cannot be static; It must have a private static property, to store the instance of the desired object.
public class Monostate { private static Object instanceOfObject; // …other properties public Monostate() {} public void setObjetoInstancia(Object object) { Monostate.ObjetoInstancia = object; } public Object getObjectInstance() { return Monostate.ObjectInstance; } // …other methods } Observing the structure of its implementation, we can notice that Monostate, in addition to needing to be instantiated wherever it is used, does not control the life cycle of its object instance.
Therefore, this control must also be implemented by the code that consumes it. This brings three main advantages to those who use it: It does not break SOLID's Single Responsibility Principle. Consequently, we have its benefits available; Because it needs to be instantiated to consume the single instance of the object, which is private in relation to Monostate, it can be considered a more transparent solution than Singleton; Even though there is Monostate getter call competition, there is no internal control that creates an instance for you. Therefore, it is more difficult for the problem of unintentionally creating instances of your object to occur ─ and if it does occur, it is probably related to the invocation of the setter in the code that consumes it.
In terms of comparison, there is not much to look at here. It is often said that Monostate and Singleton are two sides of the same coin, with the detail of the type of category that each falls into.
Monostate is a behavioral design pattern, while Singleton is creational. Still, it is worth knowing what is the best design pattern for each situation.
Are you curious? Want to know more about Monostate? Read Uncle Bob's full article on this subject at: SINGLETON and MONOSTATE.
Singleton versus Dependency Injection While one of the biggest premises of Singleton is its convenience, transparency is the key to dependency injection.
The logic behind this topic is very simple to understand: if a class or method requires a certain object to perform its operations, this object must be injected as a dependency.
By rewriting the usuarioPodeCadastrarNovosClientes() method, from the service described above, we can, instead of recovering the user from a Singleton, make explicit the dependency that the method needs on a User. This is also known as “passing object by reference”.
public class Servico { public boolean usuarioPodeCadastrarNovosClientes(Usuario usuario) { return usuarioLogado != null && usuarioEhAdmin(usuario)); } private boolean usuarioEhAdmin(Usuario usuario) { return “ADMIN“.equals(usuario.getPermissao()); } } The service does not need to worry about where the user comes from. This is a concern that the customer ─ the one who consumes this service ─ must have.
With this small change, the service became: Transparent: it is clear what its dependencies are and how it handles them; Easy to test: without using Singleton, our problem of running tests in parallel no longer exists.
Finally, dependency injection also serves to cause reflections in whoever is writing the code, bringing very interesting insights. An example would be to reflect on whether our usuarioPodeCadastrarNovosClientes method really needs the User object, or whether just the permission String is enough; In fact, is there really a need for a method in our service to carry out this verification? What if the User object itself has an internal method to validate this rule? Questions like these are 100% pertinent, and can naturally occur as soon as the dependencies of that piece of code are explicit.
Author's opinion As mentioned at the beginning of this article, every project pattern has implementation costs and benefits (trade-offs). It is important to know the “good side” and the “bad side” before we judge or defend an idea.
I wrote this article, in large part, because every place I searched, I found new information with very few explanations to justify what was being said. This article, therefore, serves as study material to consolidate all this information in a centralized location.
Most authors take one side. However, on some occasions, there is no way: there are situations where you need a single instance of an object, and that instance needs to be global in scope. But, more important than needing to use a resource, is understanding how that same resource works.
Special thanks Writing this text was special for me, and many people helped me to achieve the result. So, I would like to say some thanks.
I would first like to thank my wife for taking the time to read and re-read versions of this text. Even though I wasn't from the area, it was a very important piece and one of my biggest motivators to continue writing. I would also like to thank my colleagues Fernando Costa Leite and Felipe Pereira Maragno, for giving me some valuable feedback during the development of the article; and to Fabio Domingues and Francisco Hillesheim for supporting the writing of the advantages that Singleton brings to the developer, as well as adding to the idea of a thread-safe solution. Last but not least, I would also like to thank Cleidir Cristiano Back, Vinicius Roggia Gomes, and Renato Mendes Viegas for reviewing the final result: you were fundamental in refining the article.
Did you enjoy learning a little more about the Singleton controversy? Tell me your opinion here in the comments! Check out more content like this on our Blog.
Want to be our next Tech Writer?