Noté que casi cada vez que veo programadores que usan clases estáticas en lenguajes orientados a objetos como C #, lo están haciendo mal. Los principales problemas son obviamente el estado global y la dificultad de intercambiar implementaciones en tiempo de ejecución o con simulacros / stubs durante las pruebas.
Por curiosidad, he visto algunos de mis proyectos seleccionando los que están bien probados y cuando hice un esfuerzo por pensar realmente en la arquitectura y el diseño. Los dos usos de las clases estáticas que he encontrado son:
La clase de utilidad, algo que preferiría evitar si escribiera este código hoy,
El caché de la aplicación: usar una clase estática para eso es simplemente incorrecto. ¿Qué pasa si quiero reemplazarlo por
MemoryCache
Redis?
Al mirar .NET Framework, tampoco veo ningún ejemplo de uso válido de clases estáticas. Por ejemplo:
File
La clase estática hace que sea realmente doloroso cambiar a alternativas. Por ejemplo, ¿qué sucede si uno necesita cambiar a Almacenamiento aislado o almacenar archivos directamente en la memoria o necesita un proveedor que pueda admitir transacciones NTFS o al menos poder manejar rutas de más de 259 caracteres ? Tener una interfaz común y múltiples implementaciones parece tan fácil como usarloFile
directamente, con el beneficio de no tener que reescribir la mayor parte del código base cuando cambian los requisitos.Console
La clase estática hace que las pruebas sean demasiado complicadas. ¿Cómo debo asegurarme dentro de una prueba unitaria de que un método genera datos correctos en la consola? ¿Debo modificar el método para enviar la salida a un objeto de escrituraStream
que se inyecta en tiempo de ejecución? Parece que una clase de consola no estática sería tan simple como una estática, una vez más, sin todos los inconvenientes de una clase estática.Math
la clase estática tampoco es un buen candidato; ¿Qué pasa si necesito cambiar a aritmética de precisión arbitraria? ¿O a alguna implementación más nueva y más rápida? ¿O a un trozo que dirá, durante una prueba de unidad, que un método dado de unaMath
clase fue efectivamente llamado durante una prueba?
En Programmer.SE, he leído:
¿No utiliza "estático" en C #? Algunas respuestas están bastante en contra de las clases estáticas. Otros afirman que "los métodos estáticos están bien para usar y tienen un lugar legítimo en la programación", pero no lo cuecen con argumentos.
Cuándo usar un Singleton y cuándo usar una clase estática . Se mencionan las clases de utilidad, dado que mencioné anteriormente que las clases de utilidad son problemáticas.
¿Por qué y cuándo debo hacer una clase 'estática'? ¿Cuál es el propósito de la palabra clave 'estática' en las clases? El único uso válido que se menciona es un contenedor para métodos de extensión. Excelente.
Aparte de los métodos de extensión, ¿cuáles son los usos válidos de las clases estáticas, es decir, casos en los que la inyección de dependencia o los singletons son imposibles o darán como resultado un diseño de menor calidad y una mayor extensibilidad y mantenimiento?
fuente
instanceof
) porque en dos casosIFoo
no hay garantía de que estén respaldados por la misma clase.YAGNI
, y acepta que si alguna vez necesita un tipo diferente de cadena, sólo obtendrá su IDE para cambiar todos los casos decom.lang.lib.String
quecom.someones.elses.String
en toda la base de código.Respuestas:
YAGNI
Seriamente. Si alguien quiere usar diferentes implementaciones de
File
oMath
oConsole
después pueden pasar por el dolor de la abstracción que de distancia. ¿Has visto / usado Java?!? Las bibliotecas estándar de Java son un buen ejemplo de lo que sucede cuando abstrae cosas por el bien de la abstracción. ¿ Realmente necesito seguir 3 pasos para construir mi objeto Calendario ?Dicho todo esto, no me voy a sentar aquí y defender fuertemente las clases estáticas. El estado estático es completamente malo (incluso si todavía lo uso ocasionalmente para agrupar / almacenar cosas en caché). Las funciones estáticas pueden ser malas debido a problemas de concurrencia y capacidad de prueba.
Pero las funciones estáticas puras no son tan terribles. No son cosas elementales que no tienen sentido para salir abstracta ya que no hay alternativa cuerda para la operación. No son puestas en práctica de una estrategia que puede ser estática, porque ya están abstraídos por medio del delegado / funtor. Y a veces estas funciones no tienen una clase de instancia con la que asociarse, por lo que las clases estáticas están bien para ayudar a organizarlas.
Además, los métodos de extensión son de uso práctico , incluso si no son clases filosóficamente estáticas.
fuente
Console
realmente no debería ser parte del estado de todo el sistema, pero tampoco debería ser parte de un objeto que se pasa constantemente. En cambio, parece que tendría más sentido decir que cada subproceso tiene una propiedad de "consola actual" que se puede guardar y restaurar fácilmente si es necesario tener una rutina que escriba paraConsole
usar otra cosa.En una palabra, simplicidad.
Cuando desacoplas demasiado, obtienes el hola mundo del infierno.
Pero bueno, al menos no hay una configuración XML, lo cual es un buen toque.
La clase estática permite optimizar para el caso rápido y común. La mayoría de los puntos que mencionó se pueden resolver fácilmente introduciendo su propia abstracción sobre ellos o utilizando cosas como Console.Out.
fuente
El caso de los métodos estáticos: Esto:
es mucho más simple, claro y legible que esto:
Ahora agregue la inyección de dependencia para ocultar las instancias explícitas, y vea que la línea única se convierte en decenas o cientos de líneas, todo para respaldar el escenario poco probable de que invente su propia
Max
implementación, que es significativamente más rápida que la del marco y necesita para poder cambiar de forma transparente entre lasMax
implementaciones alternativas .El diseño API es una compensación entre simplicidad y flexibilidad. Puede siempre introducir capas adicionales de indirección ( hasta el infinito ), pero hay que sopesar la conveniencia de la causa común contra el beneficio de capas adicionales.
Por ejemplo, siempre puede escribir su propio contenedor de instancias alrededor de la API del sistema de archivos si necesita poder cambiar de forma transparente entre proveedores de almacenamiento. Pero sin los métodos estáticos más simples, todos se verían obligados a crear una instancia cada vez que tuvieran que hacer algo tan simple como guardar un archivo. Sería realmente una mala compensación de diseño optimizar para un caso extremo extremadamente raro, y hacer que todo sea más complicado para el caso común. Lo mismo con
Console
: puede crear fácilmente su propio contenedor si necesita abstraer o burlarse de la consola, pero a menudo la usa para soluciones rápidas o para depurar, por lo que solo desea la forma más simple posible de generar texto.Además, si bien "una interfaz común con múltiples implementaciones" es genial, no necesariamente hay una abstracción "correcta" que unifique a todos los proveedores de almacenamiento que desee. Es posible que desee cambiar entre archivos y almacenamiento aislado, pero alguien más puede querer cambiar entre archivos, bases de datos y cookies para el almacenamiento. La interfaz común se vería diferente, y es imposible predecir todas las posibles abstracciones que los usuarios puedan desear. Es mucho más flexible proporcionar métodos estáticos como los "primitivos", y luego permitir a los usuarios construir sus propias abstracciones y envoltorios.
Tu
Math
ejemplo es aún más dudoso. Si desea cambiar a matemática de precisión arbitraria, presumiblemente necesitará nuevos tipos numéricos. Esto sería un cambio fundamental de que todo el programa, y tener que cambiarMath.Max()
aArbitraryPrecisionMath.Max()
es sin duda el más pequeño de que las preocupaciones.Dicho esto, estoy de acuerdo en que las clases estáticas con estado son en la mayoría de los casos malvadas.
fuente
En general, los únicos lugares donde usaría clases estáticas son aquellos donde la clase aloja funciones puras que no cruzan los límites del sistema. Me doy cuenta de que esto último es redundante, ya que cualquier cosa que cruza un límite probablemente no sea pura por intención. Llego a esta conclusión tanto por la desconfianza del estado global como por la facilidad de probar la perspectiva. Incluso cuando hay una expectativa razonable de que habrá una implementación única para una clase que cruza un límite del sistema (red, sistema de archivos, dispositivo de E / S), elijo usar instancias en lugar de clases estáticas debido a la capacidad de proporcionar un simulacro / la implementación falsa en pruebas unitarias es crítica en el desarrollo de pruebas unitarias rápidas sin acoplamiento a dispositivos reales. En los casos en que el método es una función pura sin acoplamiento a un sistema / dispositivo externo, creo que una clase estática puede ser apropiada, pero solo si no obstaculiza la capacidad de prueba. Encuentro que los casos de uso son relativamente raros fuera de las clases de framework existentes. Además, puede haber casos en los que no tenga otra opción, por ejemplo, métodos de extensión en C #.
fuente