Las clases estáticas están bien siempre que se utilicen en los lugares correctos.
A saber: Métodos que son métodos 'hoja' (no modifican el estado, simplemente transforman la entrada de alguna manera). Buenos ejemplos de esto son cosas como Path.Combine. Este tipo de cosas son útiles y contribuyen a la sintaxis de Terser.
Los problemas que tienen con la estática son numerosas:
En primer lugar, si tiene clases estáticas, las dependencias se oculta. Considere lo siguiente:
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 }
}
En cuanto a TextureManager, no se puede saber qué pasos de inicialización debe ser llevada a cabo por mirar a un constructor. Debe profundizar en la clase para encontrar sus dependencias e inicializar las cosas en el orden correcto. En este caso, necesita que ResourceLoader se inicialice antes de ejecutarse. Ahora escala esta pesadilla de dependencia y probablemente puedas adivinar lo que sucederá. Imagine tratar de mantener el código donde no hay un orden explícito de inicialización. Contraste esto con la inyección de dependencia con instancias - en ese caso el código ni siquiera compilará si las dependencias no se cumplen.
Además, si usa estadísticas que modifican el estado, es como un castillo de naipes. Nunca se sabe quién tiene acceso a qué, y el diseño tiende a parecerse a un monstruo de espagueti.
Finalmente, y lo que es igual de importante, el uso de estática vincula un programa a una implementación específica. El código estático es la antítesis del diseño para la capacidad de prueba. El código de prueba plagado de estática es una pesadilla. Una llamada estática nunca se puede intercambiar por una prueba doble (a menos que utilice marcos de prueba específicamente diseñados para simular tipos estáticos), por lo que un sistema estático hace que todo lo que lo usa sea una prueba de integración instantánea.
En resumen, estática está bien para algunas cosas y para herramientas pequeñas o código desechable no desaconsejaría su uso. Sin embargo, más allá de eso, son una pesadilla sangrienta para la mantenibilidad, el buen diseño y la facilidad de las pruebas.
He aquí un buen artículo sobre los problemas: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/
Este fue exactamente el problema que tuve que resolver: el uso, o más bien mal uso, de objetos Singleton. – overslacked
Gracias por la excelente respuesta. Mi pregunta es, si los singletons se pasan como parámetros a los métodos estáticos, ¿eso hace que el método estático sea inseguro? –
Los términos "función pura" y "función impura" son nombres dados en la programación funcional a lo que usted llama estática "segura" y "insegura". – Omnimike