Inyección de dependencia versus métodos estáticos

21

Tuve una discusión interesante hoy con otro desarrollador sobre cómo abordar una clase con un método que acepta una cadena y genera una cadena.

Imagine algo como lo siguiente que está completamente inventado con el propósito de ejemplo

public string GetStringPart(string input)
{ 
   //Some input validation which is removed for clarity

   if(input.Length > 5)
        return input.Substring(0,1);

   if(input.Substring(0,1) == "B")
        return input.Substring(0,3);

   return string.empty;
}

Una función que tiene cierta lógica basada en su entrada de cadena se agrega a un proyecto usando DI y tiene un Contenedor DI en su lugar. ¿Agregarías esta nueva clase con una interfaz y la inyectarías donde sea necesario, o la convertirías en una clase estática? ¿Cuáles son los pros y los contras de cada uno? ¿Por qué querrías (o no) hacer que esto se use con la inyección del constructor en lugar de solo acceder cuando sea necesario en cualquier lugar?

James
fuente
1
@Ewan Para métodos de ayuda que no tienen uso para la abstracción. Ejemplo: Apache FileUtils.
Walfrat
3
@Ewan: los métodos estáticos sin efectos secundarios son el mejor tipo de métodos, porque son fáciles de entender y de probar.
JacquesB
1
pero ellos hacen que sea imposible probar cosas unitarias que dependen de ellos
Ewan
3
@Ewan Eh, no realmente. ¿Math.Max ​​() es malo porque es estático? Si prueba su método estático y está funcionando, puede usarlo de manera segura en sus otros métodos sin problemas. Si el estático falla, su prueba lo detectará.
T. Sar - Restablece a Mónica el
1
si 'estático' no garantizara efectos secundarios, podría ver el argumento. Pero no lo hace.
Ewan

Respuestas:

25

No hay ninguna razón por la que esto deba inyectarse. Esto es solo una función, no tiene dependencias, así que simplemente llámelo. Incluso puede ser estático si lo desea, ya que parece ser puro. Uno puede escribir pruebas unitarias contra esto sin dificultad. Si se usa en otras clases, las pruebas unitarias aún se pueden escribir.

No es necesario abstraer las funciones sin dependencias, es excesivo.

Si esto se vuelve más complejo, tal vez se justifique pasar una interfaz a un constructor o método. Pero, no seguiría ese camino a menos que tuviera una GetStringPartlógica compleja basada en la ubicación, etc.

Jon Raynor
fuente
2
¡Esta es la respuesta correcta! Para mal la respuesta de culto de carga ha sido aceptada.
JacquesB
3
que la función no tiene dependencias no es el punto. el problema es cuando otras cosas dependen de la función y se convierten estrechamente unidas a ella
Ewan
2
¿Qué pasa si la función cambia en 6 meses y no es tan pura, pero ya se usa en muchos lugares? No es extensible como estático y tampoco es algo que pueda aislarse de las pruebas unitarias de las clases consumidoras. Si incluso hay una pequeña posibilidad de cambio, iría a inyectarlo. Dicho esto, estoy abierto a escuchar argumentos opuestos, ese es el punto de esta publicación. ¿Cuáles son las desventajas de esta inyección y los aspectos positivos de una estática?
James
1
En términos generales, las personas que discuten la inyección de dependencia en este sitio web no necesitan inyectarla. De ahí el sesgo hacia una complejidad innecesaria.
Frank Hileman
2
@James Si la función se vuelve menos pura, estás haciendo algo mal y la función probablemente no estaba bien definida desde el principio. Calcular el área de un cuadrado no debería necesitar repentinamente una llamada a la base de datos o implementar una actualización visual para la interfaz de usuario. ¿Te imaginas un escenario en el que un método de "Subcadena" se vuelva impuro de repente?
T. Sar - Restablece a Mónica el
12

Este es el por qué

class DOSClient {
    OrderParser orderParser;
    string orderCode;

    DOSClient(OrderParser orderParser, string ordercode) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
    }
    void DisplayOrderCode() {
        Console.Write( "Prefix: " + orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

class GUIClient {
    OrderParser orderParser;
    string orderCode;
    GUI gui;

    GUIClient(OrderParser orderParser, string ordercode, GUI gui) { 
        this.orderParser = orderParser; 
        this.ordercode = ordercode;
        this.gui = gui;
    }

    void DisplayOrderCode() {
        gui.Prefix( orderParser.GetStringPart(ordercode) ); 
        ...
    }
}

 

class OrderParserUS : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 5)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "B")
            return input.Substring(0,3);

        return string.empty;
    }
}

class OrderParserEU : IOrderParser {

    public string GetStringPart(string input)
    { 
        //Some input validation which is removed for clarity

        if(input.Length > 6)
            return input.Substring(0,1);

        if(input.Substring(0,1) == "#")
            return input.Substring(0,3);

        return string.empty;
    }
}

Si hubiera seguido un método estático, no habría forma de cambiar el comportamiento GetStringPartsin destruir el comportamiento anterior o contaminarlo con lógica condicional. Es cierto que la estática es un malvado disfrazado, pero el hecho de que deshabilite el polimorfismo es mi principal queja sobre ellos. Los métodos estáticos no son de primera clase en lenguajes OOP. Al darle al método un objeto para vivir, incluso uno sin estado, hacemos que el método sea portátil. Su comportamiento se puede transmitir como el valor de una variable.

Aquí he imaginado un sistema que debe comportarse de manera ligeramente diferente cuando se implementa en Europa que cuando se implementa en los EE. UU. En lugar de forzar a cualquiera de los sistemas a contener el código que solo necesita el otro, podemos cambiar el comportamiento controlando qué objeto de análisis de orden se inyecta en los clientes. Esto nos permite contener la difusión de los detalles de la región. También facilita agregar OrderParserCanada sin tener que tocar los analizadores existentes.

Si eso no significa nada para ti, entonces realmente no hay un buen argumento para esto.

Por cierto, GetStringPartes un nombre terrible.

naranja confitada
fuente
* Fruncido en el estilo del código de paréntesis * Supongo que eres un programador de Java que intenta escribir código C #? Hace falta conocer uno, supongo. :)
Neil
La pregunta no es específica de un lenguaje, más sobre diseño. Mi problema con los métodos estáticos es que evitan el aislamiento del código para las pruebas. El desarrollador con el que estaba hablando cree que confío demasiado en la DI y, a veces, los métodos de extensión / métodos estáticos son relevantes. Creo que posiblemente en casos raros, pero no como un propósito general. La discusión en curso que sentí que era buena para discutir más a fondo. También estoy de acuerdo con su caso para el polimorfismo.
James
Las pruebas son una razón, pero hay más. No me gusta culpar demasiado a las pruebas porque solo hace que las personas critiquen las pruebas. El simple hecho es que a los programadores de procedimientos no les gusta si no programan de manera procesal.
candied_orange
¿No podrías simplemente decir static getStringPartEU()? Su ejemplo solo tiene sentido si hay otros métodos en esa clase que también requieren el tratamiento especializado de la UE y deben tratarse como una sola unidad.
Robert Harvey
2
Definitivamente estás argumentando a favor de una complejidad innecesaria. Todo puede tener más y más código agregado. Las cosas que deben cambiar deben ser fáciles de modificar, pero no debe intentar predecir qué necesitará cambiar en el futuro. Los campos estáticos pueden ser idénticos a las variables globales, pero los métodos estáticos pueden ser puros, el mejor tipo de método posible.
Frank Hileman