¿Banderas de método como argumentos o como variables miembro?

8

Creo que el título "¿Indicadores de método como argumentos o como variables miembro?" puede ser subóptimo, pero como me falta algún atm de terminología mejor, aquí va:

Actualmente estoy tratando de entender el problema de si las banderas para un método de clase ( privado ) deben pasarse como argumentos de función o mediante una variable miembro y / o si hay algún patrón o nombre que cubra este aspecto y / o si esto sugiere algunos otros problemas de diseño.

Por ejemplo (el lenguaje podría ser C ++, Java, C #, realmente no importa en mi humilde opinión):

class Thingamajig {
  private ResultType DoInternalStuff(FlagType calcSelect) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (calcSelect == typeA) {
        ...
      } else if (calcSelect == typeX) {
        ...
      } else if ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(FlagType calcSelect) {
    ...
    DoInternalStuff(calcSelect);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(typeA);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(typeX);
    ... some more code ... further process x ...
    return x;
  }
}

Lo que vemos arriba es que el método InternalStuffInvokertoma un argumento que no se usa dentro de esta función, sino que solo se reenvía al otro método privado DoInternalStuff. (Dónde DoInternalStuffse utilizará de forma privada en otros lugares de esta clase, por ejemplo, en el DoThatStuffmétodo (público)).

Una solución alternativa sería agregar una variable miembro que lleve esta información:

class Thingamajig {
  private ResultType DoInternalStuff() {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (m_calcSelect == typeA) {
        ...
      } ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker() {
    ...
    DoInternalStuff();
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    m_calcSelect = typeA;
    InternalStuffInvoker();
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    m_calcSelect = typeX;
    ResultType x = DoInternalStuff();
    ... some more code ... further process x ...
    return x;
  }
}

Especialmente para cadenas de llamadas profundas donde el indicador de selección para el método interno se selecciona afuera, el uso de una variable miembro puede hacer que las funciones intermedias sean más limpias, ya que no necesitan llevar un parámetro de paso.

Por otro lado, esta variable miembro no representa realmente ningún estado de objeto (ya que no está establecida ni disponible fuera), sino que es realmente un argumento adicional oculto para el método privado "interno".

¿Cuáles son los pros y los contras de cada enfoque?

Martin Ba
fuente
1
Tenga en cuenta que si tiene la necesidad de pasar banderas, probablemente tenga un montón de funciones que hacen considerablemente más de una cosa. Puede considerar enderezar la forma en que fluye el control para no tener que ofuscarlo con banderas.
cHao
También quiero mencionar que "DoInternalStuff" y "InternalStuff invocador" son nombres realmente malos. :) Sí, sé que es solo un ejemplo, pero es un poco siniestro. Si su código real hace algo tan vago como "cosas internas", ese es un problema mayor. Solucione eso, y es más probable que se revele alguna forma de simplificar el flujo de control.
cHao
1
Estoy de acuerdo con @cHao: donde he tenido situaciones como esta en el pasado, la bandera de opciones que se pasa con frecuencia era un olor a código que se estaba violando el principio de responsabilidad única .
Bevan

Respuestas:

15

Por otro lado, esta variable miembro no representa realmente ningún estado de objeto (ya que no está establecida ni disponible fuera), sino que es realmente un argumento adicional oculto para el método privado "interno".

Eso refuerza mi intuición al leer su pregunta: trate de mantener esta información lo más local posible, es decir, no la agregue al estado del objeto . Esto reduce el espacio de estado de su clase, haciendo que su código sea más fácil de entender, probar y mantener. Sin mencionar que evitar el estado compartido también hace que su clase sea más segura: esto puede o no ser una preocupación para usted en este momento, pero puede marcar una gran diferencia en el futuro.

Por supuesto, si necesita pasar estas banderas en exceso, puede convertirse en una molestia. En este caso, puede considerar

  • crear objetos de parámetros para agrupar parámetros relacionados en un solo objeto, y / o
  • encapsulando este estado y la lógica asociada en una familia separada de estrategias . La viabilidad de esto depende del lenguaje: en Java, debe definir una interfaz distinta y una clase de implementación (posiblemente anónima) para cada estrategia, mientras que en C ++ o C # puede usar punteros de función, delegados o lambdas, lo que simplifica el código importantemente.
Péter Török
fuente
Gracias por mencionar la estrategia / enfoque lambda. Tendré que mantener esa solución para cuando las cosas tengan que estar al revés :-)
Martin Ba
6

Definitivamente votaría por la opción "argumento" - mis razones:

  1. Sincronización: ¿qué sucede si se llama al método desde varios subprocesos al mismo tiempo? Al pasar las banderas como argumento, no tendrá que sincronizar el acceso a estas banderas.

  2. Capacidad de prueba: imaginemos que está escribiendo una prueba unitaria para su clase. ¿Cómo probará el estado interno del miembro? ¿Qué pasa si su código arroja una excepción y el miembro terminará en un estado inesperado?

  3. Separación de preocupaciones: al agregar las banderas a los argumentos de su método, está claro que no se supone que otro método lo use. No lo usará por error en algún otro método.

Sulthan
fuente
3

La primera alternativa me parece bien. Se llama a una función con un parámetro, y hace algo que depende de ese parámetro . Incluso si solo reenvía el parámetro a otra función, lo hace esperando que el resultado dependa de alguna manera del parámetro.

La segunda alternativa se siente como usar una variable global, solo dentro del alcance de la clase en lugar del alcance de la aplicación. Pero es el mismo problema ... debes leer cuidadosamente todo el código de la clase para determinar quién y cuándo está leyendo y escribiendo esos valores.

Por lo tanto, la primera alternativa gana.

Si usaría demasiados parámetros (lea: dos o más) en la primera alternativa, que se pasan todos juntos a la siguiente función, puede considerar ponerlos en un objeto (conviértalo en una clase interna, si no es relevante para el resto de la aplicación) y usar este objeto como parámetro.

Viliam Búr
fuente
1

Quiero decir "ninguno".

En primer lugar, considere que ya sabe lo que va a hacer cuando llame DoInternalStuff. La bandera dice lo mismo. Así que ahora, en lugar de seguir adelante y hacerlo , te estás tirando pedos a una función que ahora tiene que decidir qué hacer.

Si desea decirle a la función qué hacer, dígale qué hacer . Puede pasar una función real que desea ejecutar para cada iteración como un delegado de C # (o un objeto de función de C ++, o un Java ejecutable o similar).

C ª#:

class Thingamajig {
  private delegate WhateverType Behavior(ArgsType args);

  private ResultType DoInternalStuff(Behavior behavior) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      var subResult = behavior(someArgs);
      ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(Behavior behavior) {
    ...
    DoInternalStuff(behavior);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(doThisStuffPerIteration);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(doThatStuffPerIteration);
    ... some more code ... further process x ...
    return x;
  }
}

Todavía terminas con la discusión pasando por todo el lugar, pero te deshaces de la mayoría de las tonterías de if / else DoInternalStuff. También puede almacenar el delegado como un campo si lo desea.

En cuanto a si se debe pasar todo o guardarlo ... si tiene que elegir uno, entonces páselo. Es probable que almacenarlo te muerda el culo más tarde, ya que prácticamente has descuidado la seguridad del hilo. Tenga en cuenta que si tiene la intención de hacer algo con hilos, ahora tiene que bloquear durante toda la duración del ciclo, o alguien puede cambiar todo por debajo de usted. Mientras que si lo pasa, es menos probable que se tire a la basura ... y si aún necesita bloquear o algo, puede hacerlo durante una parte de una iteración en lugar de todo el ciclo.

Francamente, es bueno que sea molesto. Probablemente haya una forma mucho más simple de hacer lo que quieres hacer, que realmente no podría aconsejarte ya que el código obviamente no es real. La mejor respuesta varía según el caso.

cHao
fuente