Singleton pattern is indeed a pattern to avoid. Apart from introducing serious problems when it comes to writing unit tests, managed languages that uses garbage collection like Java and C# will suffer from potential memory leaks du to the way the garbage collector treats static references. In other words, static instances will reside in the memory for the lifetime of the application. In case the static instance also uses unmanaged resources, you block this resources and risk to keep the allocated resources alive after the application has closed.
It really depends on the actual scenario, but we can say that when applying or implementing proper programming principles we can avoid the Singleton pattern and its disadvantages.
The key is to avoid static references in general. If you have the following code that calls a static method too read from a database:
public User GetUser(int id)
{
User user = StaticDatabaseHelper.GetUser(id);
// Do something
return user;
}
You can only test this method by making real calls to the actual database. You can't fake it. You see thta if you are not writing unit tests, this disadvantage could be irrelevant. But what is always relevant is the bad design that is very likely introduced by making instances and methods globally available. The fact that the author decides to make a state (static properties or instances (Singleton) or methods (static classes) globally available is the result of missing application design. It's something beginners do as they don't know more advanced pattern and principles or architecture in general. Beginners don't care about lifecycle management or encapsulation and data hiding. Global references will scatter through out your code rapidly, making it a nightmare to maintain. The only static members should be constants.
The best way to avoid the Singleton pattern is to introduce aggregation (or dependency injection). You would typically inject/pass a shared instance to a constructor instead of exposing a static instance. This usually requires a common composition root for all the dependent instances:
class A
{
// Constructor injection
public A(Application sharedApplicationInstance)
{
this.application = sharedApplicationInstance;
}
private Application application;
}
class B
{
// Constructor injection
public A(Application sharedApplicationInstance)
{
this.application = sharedApplicationInstance;
}
private Application application;
}
public Main()
{
Application sharedApplicationInstance = new Application();
// Composition root
A a = new A(sharedApplicationInstance);
B b = new B(sharedApplicationInstance);
}