Classes estáticas funcionam, desde que sejam usadas nos lugares certos.
A saber: Métodos que são métodos 'folha' (eles não modificam o estado, eles simplesmente transformam a entrada de alguma forma). Bons exemplos disso são coisas como Path.Combine. Esses tipos de coisas são úteis e tornam a sintaxe mais concisa.
Os problemas que tenho com a estática são numerosos:
Em primeiro lugar, se você tiver classes estáticas, as dependências serão ocultadas. Considere o seguinte:
public static class ResourceLoader
{
public static void Init(string _rootPath) { ... etc. }
public static void GetResource(string _resourceName) { ... etc. }
public static void Quit() { ... etc. }
}
public static class TextureManager
{
private static Dictionary<string, Texture> m_textures;
public static Init(IEnumerable<GraphicsFormat> _formats)
{
m_textures = new Dictionary<string, Texture>();
foreach(var graphicsFormat in _formats)
{
// do something to create loading classes for all
// supported formats or some other contrived example!
}
}
public static Texture GetTexture(string _path)
{
if(m_textures.ContainsKey(_path))
return m_textures[_path];
// How do we know that ResourceLoader is valid at this point?
var texture = ResourceLoader.LoadResource(_path);
m_textures.Add(_path, texture);
return texture;
}
public static Quit() { ... cleanup code }
}
Olhando para TextureManager, você não pode dizer quais etapas de inicialização devem ser executadas olhando para um construtor. Você deve mergulhar na classe para encontrar suas dependências e inicializar as coisas na ordem correta. Nesse caso, ele precisa que o ResourceLoader seja inicializado antes de ser executado. Agora aumente esse pesadelo de dependência e provavelmente você pode adivinhar o que vai acontecer. Imagine tentar manter o código onde não há uma ordem explícita de inicialização. Compare isso com injeção de dependência com instâncias - nesse caso, o código nem mesmo compilará se as dependências não forem atendidas!
Além disso, se você usar estática que modifica o estado, é como um castelo de cartas. Você nunca sabe quem tem acesso a quê, e o design tende a se parecer com um monstro espaguete.
Finalmente, e tão importante, o uso da estática vincula um programa a uma implementação específica. O código estático é a antítese do design para testabilidade. Testar códigos crivados de estática é um pesadelo. Uma chamada estática nunca pode ser trocada por um duplo de teste (a menos que você use estruturas de teste projetadas especificamente para simular tipos estáticos), portanto, um sistema estático faz com que tudo o que o usa seja um teste de integração instantâneo.
Resumindo, a estática é boa para algumas coisas e para pequenas ferramentas ou código descartável, eu não desencorajaria seu uso. No entanto, além disso, eles são um pesadelo sangrento para manutenção, bom design e facilidade de teste.
Aqui está um bom artigo sobre os problemas: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/