2011-05-24 15 views
18

A veces, cuando la fijación de un defecto en una base de código existente que podría (a menudo por pereza) decidir cambiar un método de:¿Alternativas para pasar una bandera a un método?

void 
MyClass::foo(uint32_t aBar) 
{ 
    // Do something with aBar... 
} 

a:

void 
MyClass::foo(uint32_t aBar, bool aSomeCondition) 
{ 
    if (aSomeCondition) 
    { 
     // Do something with aBar... 
    } 
} 

Durante una revisión de código a un colega mencionó que un mejor enfoque sería sub-clase MyClass para proporcionar esta funcionalidad especializada.

Sin embargo, yo diría que mientras aSomeCondition no viole el propósito o la cohesión de MyClass es un patrón aceptable de usar. Solo si el código se infiltra con flags y if, las declaraciones serían una mejor opción, de lo contrario estaríamos entrando potencialmente en territorio de astronautas de arquitectura.

¿Cuál es el punto de inflexión aquí?

Nota: Acabo de ver this related answer lo que sugiere que un enum puede ser una mejor opción que un bool, pero creo que mi pregunta todavía se aplica en este caso.

+2

En mi humilde opinión, no empieces. Será difícil de mantener con el tiempo. Y eventual refactorización es mucho más difícil entonces. – Keith

+0

@Keith - Disculpa, no tengo claro qué querías decir. No empieces, ¿qué? – LeopardSkinPillBoxHat

+4

No empieces a usar métodos condicionales como ese. Ya sea una subclase o un método separado que la persona que llama puede decidir. – Keith

Respuesta

24

No hay una sola solución para este tipo de problema.

Boolean tiene una semántica muy baja. Si desea agregar en el futuro una nueva condición, deberá agregar un nuevo parámetro ...
Después de cuatro años de mantenimiento, su método puede tener media docena de parámetros, si estos parámetros son todos booleanos, es una trampa muy agradable para mantenedores.

Enum es una buena opción si los casos son exclusivos. Los mensajes se pueden migrar fácilmente a una máscara de bits o a un objeto de contexto.

Máscara de bits: C++ incluye el lenguaje C, puede utilizar algunas viejas prácticas simples. En algún momento una máscara de bit en un int sin signo es una buena opción (pero se pierde la verificación de tipo) y puede pasar por error una máscara incorrecta. Es una forma conveniente de moverse sin problemas desde un argumento booleano o enum a este tipo de patrón. La máscara de bits se puede migrar con cierto esfuerzo a un objeto de contexto. Puede que tenga que implementar algún tipo de aritmética de bits como operator | y operator & si tiene que mantener una compatibilidad de compilación.

La herencia es a veces una buena elección si la división del comportamiento es grande y este comportamiento SE RELACIONA con el ciclo de vida de la instancia. Tenga en cuenta que también tiene que usar polimorfismo y esto puede ralentizar el método si este método se usa mucho.
Y, finalmente, la herencia induce el cambio en todos los códigos de fábrica ... ¿Y qué harás si tienes varios métodos para cambiar de forma exclusiva? Desordenarás tu código de clases específicas ... De hecho, creo que esta no es una muy buena idea en general.

Método dividido: Otra solución es a veces dividir el método en varios privados y proporcionar dos o más métodos públicos.

Objeto de contexto: C++ y C falta de parámetro con nombre se puede pasar por alto agregando un parámetro de contexto. Utilizo este patrón muy a menudo, especialmente cuando tengo que pasar muchos datos a través del nivel de un marco complejo.

class Context{ 
public: 
    // usually not a good idea to add public data member but to my opinion this is an exception 
    bool setup:1; 
    bool foo:1; 
    bool bar:1; 
    ... 
    Context() : setup(0), foo(0), bar(0) ... {} 
}; 
...  

Context ctx; 
ctx.setup = true; ... 
MyObj.foo(ctx); 

Nota: Que esto también es útil para minimizar el acceso (o uso) de los datos estáticos o consulta a Singleton objeto, TLS ... objeto Context puede contener una mayor cantidad de caché de datos relacionados con un algoritmo . ... dejo a tu imaginación ...

patrones Anti

añado aquí varias patrón contra (para evitar un cambio de firma): * Nunca haga esto *

  • * Nunca haga esto * utilizar un int/bool estático para pasar argumento (algunas personas que hacen eso, y esto es una pesadilla para eliminar este tipo de cosas). Interrumpir al menos los hilos múltiples ...
  • * NUNCA HAGA ESTO * agregue un miembro de datos para pasar el parámetro al método.
+3

+1 particularmente para la lista NUNCA HACER ESTA lista – sleske

+0

Hubo muchas respuestas excelentes a esta pregunta, pero esta fue la que mejor resumí para mí. – LeopardSkinPillBoxHat

18

Desafortunadamente, no creo que haya una respuesta clara al problema (y es una que encuentro con bastante frecuencia en mi propio código). Con la booleana:

foo(x, true); 

la llamada es difícil de entender.

Con una enumeración:

foo(x, UseHigherAccuracy); 

es fácil de entender, pero que tienden a terminar con un código como éste:

foo(x, something == someval ? UseHigherAccuracy : UseLowerAccuracy); 

lo cual no es una mejora. Y con múltiples funciones:

if (something == someval) { 
     AccurateFoo(x); 
} 
else { 
     InaccurateFoo(x); 
} 

usted termina con mucho más código. Pero creo que este es el más fácil de leer, y lo que tendería a usar, pero todavía no me gusta completamente :-(

Una cosa que definitivamente NO haría, sin embargo, es la subclase. la herramienta última llegar nunca para

+7

"La herencia debe ser la última herramienta que puedas alcanzar". Especialmente si es para una pequeña función como aquí. +1 para el rango de otras opciones. – Xeo

+0

C# resolvió esto con parámetros nombrados. –

4

la pauta general que utilizo es:. Si aSomeCondition cambia la naturaleza de la función de una manera importante, entonces yo considero subclases

subclases es una. esfuerzo relativamente grande comparado con agregar una bandera que tiene solo un efecto menor Connecticut.

Algunos ejemplos:

  • si se trata de un indicador que cambia la dirección en la que se devuelve una colección ordenada de la persona que llama, que es un cambio menor en la naturaleza (bandera).
  • si es one-shot indicador (algo que afecta la llamada actual en lugar de un cambio persistente en el objeto), probablemente no sea una subclase (ya que bajar esa pista es probable que conduzca a un número masivo de clases).
  • si es una enumeración que cambia la estructura de datos subyacente de su clase de una matriz a una lista vinculada o un árbol balanceado, eso es un cambio complejo (subclase).

Por supuesto, esto último puede ser mejor manejado por ocultar totalmente la estructura de datos subyacente pero estoy asumiendo aquí que desea ser capaz de seleccionar uno de muchos, por razones tales como el rendimiento.

0

En mi humilde opinión, aSomeCondition cambia o depende del estado de la instancia actual, por lo tanto, bajo ciertas condiciones esta clase debería cambiar su estado y manejar la operación mencionada de manera diferente. En este caso, puedo sugerir el uso de State Pattern. Espero eso ayude.

9

La cuestión principal es si la bandera afecta el comportamiento de la clase, o de que una función . Los cambios de función local deben ser parámetros, no subclases. La herencia en tiempo de ejecución debe ser una de las últimas herramientas a las que se llegue.

+0

+1, hasta el punto. – Xeo

Cuestiones relacionadas