¿Cuál es una mejor práctica: los métodos de ayuda como instancia o estática?

47

Esta pregunta es subjetiva, pero tenía curiosidad de cómo la mayoría de los programadores abordan esto. El ejemplo a continuación 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 de ayuda en mis clases, tiendo a declararlos como estáticos y simplemente pasar los campos si el método de ayuda los necesita. Por ejemplo, dado el código a continuación, prefiero usar Method Call # 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 hacer esto es que deja en claro (a mis ojos al menos) que el método auxiliar tiene un posible efecto secundario en otros objetos, incluso sin leer su implementación. Me parece que puedo asimilar rápidamente los métodos que utilizan esta práctica y, por lo tanto, me ayudan a depurar las cosas.

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

    
pregunta Ilian 02.10.2011 - 15:40

3 respuestas

23

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

Si son complejos, se aplica SOLID , más específicamente el S , I y la < a href="http://en.wikipedia.org/wiki/Dependency_inversion_principle"> 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 makeEveryBodyAsHappyAsPossible sea un método estático, que incorporará 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 OurHouse no necesita conocer las complejidades de las reglas de distribución de cookies. Sólo debe ahora un objeto, que implementa una regla. La implementación se abstrae en un objeto, cuya única responsabilidad es aplicar la regla. Este objeto puede ser probado de forma aislada. OurHouse se puede probar usando una mera simulación del CookieDistributor . Y puede decidir fácilmente cambiar las reglas de distribución de cookies.

Sin embargo, cuídate 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 puede tener solo una responsabilidad, sino que una sola clase debe realizar una sola responsabilidad.

En el caso de primitivos u objetos que usa como primitivos (por ejemplo, objetos que representan puntos en el espacio, matrices o algo así), las clases de ayuda estática 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 que un Point tenga un método add . De nuevo, no te excedas.

Entonces, dependiendo de tu problema, hay diferentes maneras de hacerlo.

    
respondido por el back2dos 02.10.2011 - 16:22
17

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

Sin embargo, hay una seria limitación a este enfoque: tales métodos / clases no pueden burlarse fácilmente (aunque AFAIK al menos para C # hay marcos burlones que pueden lograr incluso esto, pero no son 't commonplace y al menos algunos de ellos son comerciales). Por lo tanto, si un método auxiliar tiene una dependencia externa (por ejemplo, una base de datos) que lo hace (por lo tanto, sus llamadores) son difíciles de probar, es mejor declararlo no estático . Esto permite la inyección de dependencia, lo que hace que las personas que llaman al método sean más fáciles de probar por unidad.

Actualizar

Aclaración: lo anterior habla sobre las clases de utilidad, que contienen solo métodos de ayuda de bajo nivel y (típicamente) ningún estado. Los métodos de ayuda dentro de las clases no útiles de estado 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 Opción 1, ya que es más simple y fácil de leer.

    
respondido por el Péter Török 02.10.2011 - 15:45
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() .

  

¿Cuáles 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 # 2. con # 1, siempre tengo los miembros y la instancia disponibles. En comparación con muchas implementaciones basadas en OO, tiendo a usar mucha encapsulació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, los argumentos que uno debería pasar).

# 1 es mucho más sencillo 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 # 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 se reutiliza lo suficiente? ¿Es el nivel de abstracción suficientemente alto?

por último, el # 2 parece redundante si estás usando lenguajes OO donde se admite la mutación (por ejemplo, un método const ).

    
respondido por el justin 02.10.2011 - 16:08

Lea otras preguntas en las etiquetas