2009-04-15 18 views
77

Tiendo a declarar como estáticos todos los métodos en una clase cuando esa clase no requiere hacer un seguimiento de los estados internos. Por ejemplo, si necesito transformar A en B y no confío en algún estado interno C que puede variar, creo una transformación estática. Si hay un estado interno C que deseo poder ajustar, entonces agrego un constructor para establecer C y no uso una transformación estática.¿Está utilizando muchos métodos estáticos como algo malo?

He leído varias recomendaciones (incluso en StackOverflow) NO abusar de los métodos estáticos, pero todavía no entiendo qué está mal con la regla general anterior.

¿Es ese un enfoque razonable o no?

Respuesta

119

Hay dos tipos de métodos estáticos comunes:

  • Un método estático "segura" siempre dará la misma salida para las mismas entradas. No modifica los globales y no llama a ningún método estático "inseguro" de ninguna clase. Básicamente, estás usando un tipo limitado de programación funcional: no temas, están bien.
  • Un método estático "inseguro" muta el estado global, o proxies a un objeto global, o algún otro comportamiento no comprobable. Estos son retrocesos en la programación de procedimientos, y deben ser refactorizados si es posible.

Existen algunos usos comunes de la estática "insegura", por ejemplo, en el patrón de Singleton, pero tenga en cuenta que a pesar de los nombres bonitos que los llame, solo está mutando las variables globales. Piense con cuidado antes de usar estática insegura.

+0

Este fue exactamente el problema que tuve que resolver: el uso, o más bien mal uso, de objetos Singleton. – overslacked

+0

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? –

+0

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

3

Ese parece ser un enfoque razonable. La razón por la que no desea utilizar demasiadas clases/métodos estáticos es que termina alejándose de la programación orientada a objetos y más en el ámbito de la programación estructurada.

En el caso en el que simplemente está transformando la A a la B, dicen que todo lo que hacemos es transformar el texto para ir de

"hello" =>(transform)=> "<b>Hello!</b>" 

A continuación, un método estático tendría sentido.

Sin embargo, si invoca frecuentemente estos métodos estáticos en un objeto y tiende a ser único para muchas llamadas (por ejemplo, la forma en que lo usa depende de la entrada), o es parte del comportamiento inherente del objeto, sería conveniente hacerlo parte del objeto y mantener su estado. Una forma de hacerlo sería implementarlo como una interfaz.

class Interface{ 
    method toHtml(){ 
     return transformed string (e.g. "<b>Hello!</b>") 
    } 

    method toConsole(){ 
     return transformed string (e.g. "printf Hello!") 
    } 
} 


class Object implements Interface { 
    mystring = "hello" 

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object 

    method toHtml() 
    method toConsole() 
} 

Editar: Un buen ejemplo de un gran uso de métodos estáticos son métodos de ayuda HTML en Asp.Net MVC o Ruby. Crean elementos html que no están vinculados al comportamiento de un objeto y, por lo tanto, son estáticos.

Editar 2: Cambié la programación funcional a programación estructurada (por alguna razón me confundí), apoyos a Torsten para señalarlo.

+2

No creo que el uso de métodos estáticos califique como programación funcional, así que supongo que te refieres a la programación estructurada. –

14

Un objeto sin ningún estado interno es algo sospechoso.

Normalmente, los objetos encapsulan estado y comportamiento. Un objeto que solo encapsula el comportamiento es impar. A veces es un ejemplo de Ligero o Flyweight.

Otras veces, es un diseño de procedimiento hecho en un lenguaje de objetos.

+5

Escucho lo que dices, pero ¿cómo algo como un objeto matemático puede resumir algo más que el comportamiento? – JonoW

+8

Él acaba de decir sospechoso, no equivocado, y tiene toda la razón. –

+2

@JonoW: Math es un caso muy especial en el que hay muchas funciones sin estado.Por supuesto, si está haciendo una programación funcional en Java, tendría muchas funciones sin estado. –

1

Mientras no entre en juego el estado interno, está bien. Tenga en cuenta que, por lo general, se espera que los métodos estáticos sean seguros para subprocesos, por lo que si usa estructuras de datos auxiliares, úselos de forma segura para subprocesos.

5

La otra opción es añadir como métodos no estáticos en el objeto de origen:

es decir, el cambio de:

public class BarUtil { 
    public static Foo transform(Bar toFoo) { ... } 
} 

en

public class Bar { 
    ... 
    public Foo transform() { ...} 
} 

sin embargo, en muchas situaciones esto no es no es posible (ej., generación de código de clase regular de XSD/WSDL/etc), o hará que la clase sea muy larga, y los métodos de transformación a menudo pueden ser un verdadero dolor para objetos complejos y solo los quiere en su propia clase separada. Así que sí, tengo métodos estáticos en las clases de utilidad.

3

El motivo por el que se le advierte que no utilice métodos estáticos es que al usarlos se pierde una de las ventajas de los objetos. Los objetos están destinados a la encapsulación de datos. Esto evita que ocurran efectos secundarios inesperados que eviten errores. Los métodos estáticos no tienen datos encapsulados * y, por lo tanto, no obtienen este beneficio.

Dicho esto, si no tiene uso de datos internos, están bien de usar y un poco más rápidos de ejecutar. Sin embargo, asegúrate de no estar tocando datos globales en ellos.

  • Algunos idiomas también tienen variables de nivel de clase que permitirían la encapsulación de datos y métodos estáticos.
1

Reformé una aplicación para eliminar/modificar algunas clases que se habían implementado inicialmente como clases estáticas. Con el paso del tiempo, estas clases adquirieron tanto y la gente siguió etiquetando las nuevas funciones como estáticas, ya que nunca había una instancia flotando.

Por lo tanto, mi respuesta es que las clases estáticas no son intrínsecamente malas, pero podría ser más fácil comenzar a crear instancias ahora, luego tener que refactorizar más tarde.

0

Si sabes que nunca necesitarás usar el estado interno de C, está bien. Sin embargo, si alguna vez cambiara en el futuro, necesitaría hacer que el método no sea estático. Si para empezar no es estático, simplemente puede ignorar el estado interno si no lo necesita.

1

Solía ​​ir y venir entre una clase con un montón de métodos estáticos y un singleton. Ambos resuelven el problema, pero el singleton se puede reemplazar mucho más fácilmente con más de uno. (Los programadores siempre parecen tan seguros de que solo habrá 1 de algo y me equivoqué lo suficiente como para abandonar por completo los métodos estáticos, excepto en algunos casos muy limitados).

De todos modos, el singleton te da la posibilidad de pasar algo más tarde a la fábrica para obtener una instancia diferente y eso cambia el comportamiento de todo tu programa sin refactorizar. Cambiar una clase global de métodos estáticos en algo con diferentes datos de "respaldo" o un comportamiento ligeramente diferente (clase infantil) es un gran dolor en el trasero.

Y los métodos estáticos no tienen una ventaja similar.

Así que sí, son malas.

9

Esto es realmente solo un seguimiento de la gran respuesta de John Millikin.


Aunque puede ser seguro para hacer que los métodos sin estado (que son más o menos funciones) estática, a veces puede conducir a un acoplamiento que es difícil de modificar. Considere usted tiene un método estático como tal:

public class StaticClassVersionOne { 
    public static void doSomeFunkyThing(int arg); 
} 

que llamas como:

StaticClassVersionOne.doSomeFunkyThing(42); 

¿Qué es todo muy bien, y muy conveniente, hasta que se encuentra con un caso en el que tiene que modificar el comportamiento del método estático, y descubre que está estrechamente vinculado al StaticClassVersionOne. Posiblemente podría modificar el código y estaría bien, pero si hubiera otras personas que llamaban que dependieran del comportamiento anterior, deberán ser contabilizadas en el cuerpo del método. En algunos casos, ese método puede ser bastante feo o imposible de mantener si trata de equilibrar todos estos comportamientos. Si divide los métodos, puede tener que modificar el código en varios lugares para tenerlo en cuenta, o hacer llamadas a clases nuevas.

Pero considere si creó una interfaz para proporcionar el método, y se lo dio a quienes llaman, ahora cuando el comportamiento tiene que cambiar, se puede crear una nueva clase para implementar la interfaz, que es más limpia, más fácil de probar, y más fácil de mantener, y que en cambio se les da a las personas que llaman. En este escenario, las clases de llamada no necesitan ser alteradas o incluso recompiladas, y los cambios están localizados.

Puede o no ser una situación probable, pero creo que vale la pena considerarlo.

+4

Argumento que este no es solo un escenario probable, esto hace que la estática sea un último recurso. La estática hace que TDD sea una pesadilla también. Donde quiera que use la estática, no puede simular, debe saber cuál es la entrada y la salida para probar una clase no relacionada. Ahora, si cambia el comportamiento de la estática, sus pruebas en clases no relacionadas que usan esa estática están rotas. Además, se convierte en una dependencia oculta que no se puede pasar al constructor para notificar a los desarrolladores de una dependencia potencialmente importante. –

2

Lo consideraría un olor de diseño. Si te encuentras utilizando métodos principalmente estáticos, probablemente no tengas un diseño OO muy bueno. No es necesariamente malo, pero como con todos los olores me haría parar y volver a evaluar. Sugiere que es posible que pueda hacer un mejor diseño de OO, o que tal vez debería ir en la otra dirección y evitar OO completamente por este problema.

4

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/

1

Si se trata de un método de utilidad, es bueno para que sea estática. Guava y Apache Commons se basan en este principio.

Mi opinión sobre esto es puramente pragmática. Si es su código de aplicación, los métodos estáticos generalmente no son lo mejor que se puede tener. Los métodos estáticos tienen serias limitaciones de pruebas unitarias; no se pueden burlar fácilmente: no se puede inyectar una funcionalidad estática burlada en alguna otra prueba. Tampoco puedes inyectar funcionalidad en un método estático.

Por lo tanto, en mi lógica de aplicación generalmente tengo pequeñas llamadas a métodos estáticos similares a las de la utilidad. Es decir.

static cutNotNull(String s, int length){ 
    return s == null ? null : s.substring(0, length); 
} 

una de las ventajas es que no prueba tales métodos :-)

1

Bueno, no hay una bala de plata, por supuesto. Las clases estáticas están bien para pequeñas utilidades/ayudantes. Pero usar métodos estáticos para la programación de lógica de negocios es ciertamente malo. Considere el siguiente código

public class BusinessService 
    { 

     public Guid CreateItem(Item newItem, Guid userID, Guid ownerID) 
     { 
      var newItemId = itemsRepository.Create(createItem, userID, ownerID); 
      **var searchItem = ItemsProcessor.SplitItem(newItem);** 
      searchRepository.Add(searchItem); 
      return newItemId; 
     } 
    } 

Ves una llamada a un método estático para ItemsProcessor.SplitItem(newItem); Huele causa

  • Usted no tiene dependencia explícita declarado y si no profundizar en código que podría pasar por alto el acoplamiento entre su clase y contenedor de método estático
  • No puede probar BusinessService aislándolo de ItemsProcessor (la mayoría de las herramientas de prueba no se burlan de las clases estáticas) y hace que las pruebas de unidad sean imposibles. No hay pruebas unitarias == baja calidad
0

Los métodos estáticos son generalmente una mala opción incluso para el código sin estado. En su lugar, realice una clase singleton con estos métodos que se instancia una vez y se inyecta en aquellas clases que desean utilizar los métodos. Tales clases son más fáciles de burlar y probar. Están mucho más orientados a objetos. Puede envolverlos con un proxy cuando sea necesario. Las estadísticas hacen que OO sea mucho más difícil y no veo razón para usarlas en casi todos los casos. No es 100% sino casi todo.