¿Cuál es una mejor práctica: métodos auxiliares como instancia o estáticos?

48

Esta pregunta es subjetiva, pero tenía curiosidad por saber cómo la mayoría de los programadores abordan esto. El siguiente ejemplo está en pseudo-C #, pero esto también debería aplicarse a Java, C ++ y otros lenguajes OOP.

De todos modos, cuando escribo métodos auxiliares en mis clases, tiendo a declararlos como estáticos y simplemente pasar los campos si el método auxiliar los necesita. Por ejemplo, dado el siguiente código, prefiero usar la Llamada de método n. ° 2 .

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Mi razón para hacerlo es que deja en claro (al menos a mis ojos) que el método auxiliar tiene un posible efecto secundario sobre otros objetos, incluso sin leer su implementación. Me parece que puedo asimilar rápidamente métodos que utilizan esta práctica y, por lo tanto, me ayudan a depurar cosas.

¿Qué prefieres hacer en tu propio código y cuáles son tus razones para hacerlo?

Ilian
fuente
3
Quitaría C ++ de esta pregunta: en C ++, obviamente, lo convertirías en un método gratuito, porque no tiene sentido que se oculte arbitrariamente en un objeto: deshabilita ADL.
Matthieu M.
1
@MatthieuM .: Método libre o no, no importa. La intención de mi pregunta era preguntar si hay ventajas en declarar métodos auxiliares como ejemplos. Entonces, en C ++, la elección podría ser método de instancia versus método / función libre. Tienes un buen punto sobre ADL. Entonces, ¿estás diciendo que preferirías usar métodos gratuitos? ¡Gracias!
Ilian
2
para C ++, se recomiendan métodos gratuitos. Aumentan la encapsulación (indirectamente, ya que solo pueden acceder a la interfaz pública) y siguen siendo agradables debido a ADL (especialmente en plantillas). Sin embargo, no sé si esto es aplicable a Java / C #, C ++ difiere mucho de Java / C #, por lo que espero que las respuestas difieran (y, por lo tanto, no creo que tenga sentido pedir los 3 idiomas juntos).
Matthieu M.
1
Sin tratar de agravar a nadie, nunca he escuchado una buena razón para no usar métodos estáticos. Los uso siempre que puedo, ya que hacen que sea más obvio lo que hace el método.
Sridhar Sarnobat

Respuestas:

23

Esto realmente depende. Si los valores con los que operan sus ayudantes son primitivos, entonces los métodos estáticos son una buena opción, como señaló Péter.

Si ellos son complejos y, a continuación SÓLIDO aplica, más concretamente, el S , el I y el D .

Ejemplo:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Esto sería sobre tu problema. Usted puede hacer que makeEveryBodyAsHappyAsPossibleun método estático, que se llevará en los parámetros necesarios. Otra opción es:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Ahora OurHouseno es necesario conocer las complejidades de las reglas de distribución de cookies. Solo debe ser ahora un objeto, que implementa una regla. La implementación se abstrae en un objeto, cuya única responsabilidad es aplicar la regla. Este objeto se puede probar de forma aislada. OurHousese puede probar con el uso de un simple simulacro de CookieDistributor. Y puede decidir fácilmente cambiar las reglas de distribución de cookies.

Sin embargo, tenga cuidado de no exagerar. Por ejemplo, tener un sistema complejo de 30 clases actúa como la implementación de CookieDistributor, donde cada clase simplemente cumple una pequeña tarea, realmente no tiene sentido. Mi interpretación del SRP es que no solo dicta que cada clase solo puede tener una responsabilidad, sino que una sola clase debe llevar a cabo una sola responsabilidad.

En el caso de las primitivas u objetos que usa como primitivas (por ejemplo, objetos que representan puntos en el espacio, matrices o algo), las clases auxiliares estáticas tienen mucho sentido. Si tiene la opción, y realmente tiene sentido, entonces podría considerar agregar un método a la clase que representa los datos, por ejemplo, es sensato Pointtener un addmétodo. De nuevo, no exagere.

Entonces, dependiendo de su problema, hay diferentes maneras de solucionarlo.

back2dos
fuente
18

Es un lenguaje bien conocido declarar métodos de clases de utilidad static, por lo que tales clases nunca necesitan ser instanciadas. Seguir este modismo hace que su código sea más fácil de entender, lo cual es algo bueno.

Sin embargo, hay una seria limitación a este enfoque: tales métodos / clases no pueden ser fácilmente burlados (aunque AFAIK al menos para C # hay marcos de trabajo que pueden lograr incluso esto, pero no son comunes y al menos algunos de ellos son comercial). Por lo tanto, si un método auxiliar tiene una dependencia externa (por ejemplo, una base de datos) que hace que, por lo tanto, sus llamantes, sean difíciles de probar, es mejor declararlo como no estático . Esto permite la inyección de dependencia, lo que hace que los llamadores del método sean más fáciles de realizar pruebas unitarias.

Actualizar

Aclaración: lo anterior se refiere a clases de utilidad, que contienen solo métodos auxiliares de bajo nivel y (típicamente) ningún estado. Los métodos auxiliares dentro de las clases con estado sin utilidad son un problema diferente; disculpas por malinterpretar el OP.

En este caso, percibo un olor a código: un método de clase A que opera principalmente en una instancia de clase B puede tener un mejor lugar en la clase B. Pero si decido mantenerlo donde está, prefiero la opción 1, ya que es más simple y fácil de leer.

Péter Török
fuente
¿No puedes pasar la dependencia al método auxiliar estático?
Ilian
1
@JackOfAllTrades, si el único propósito del método es tratar con un recurso externo, casi no tiene sentido inyectar esa dependencia. De lo contrario, puede ser una solución (aunque en el último caso, el método probablemente rompe el Principio de Responsabilidad Única, por lo tanto, es un candidato para la refactorización).
Péter Török
1
las estadísticas se pueden 'burlar' fácilmente: Microsoft Moles o Fakes (según VS2010 o VS2012 +) le permite reemplazar un método estático con su propia implementación en sus pruebas. Hace que los viejos marcos burlones queden obsoletos. (también hace simulacros tradicionales, y también puede imitar clases concretas). Está libre de MS a través del administrador de extensiones.
gbjbaanb
4

¿Qué prefieres hacer en tu propio código?

Prefiero # 1 ( this->DoSomethingWithBarImpl();), a menos que, por supuesto, el método auxiliar no necesite acceso a los datos / implementación de la instancia static t_image OpenDefaultImage().

y cuales son tus razones para hacerlo?

La interfaz pública y la implementación privada y los miembros están separados. los programas serían mucho más difíciles de leer si optara por el n. ° 2. con # 1, siempre tengo los miembros y la instancia disponibles. en comparación con muchas implementaciones basadas en OO, tiendo a usar mucha encapsualización y muy pocos miembros por clase. En cuanto a los efectos secundarios, estoy de acuerdo con eso, a menudo hay una validación de estado que extiende las dependencias (por ejemplo, argumentos que uno tendría que pasar).

# 1 es mucho más simple de leer, mantener y tiene buenos rasgos de rendimiento. por supuesto, habrá casos en los que puede compartir una implementación:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Si el # 2 fuera una buena solución común (por ejemplo, la predeterminada) en una base de código, me preocuparía la estructura del diseño: ¿las clases están haciendo demasiado? ¿No hay suficiente encapsulación o no hay suficiente reutilización? ¿Es el nivel de abstracción lo suficientemente alto?

Por último, # 2 parece redundante si está utilizando lenguajes OO donde se admite la mutación (por ejemplo, un constmétodo).

justin
fuente