2010-03-12 11 views
69

De vez en cuando, tenemos que escribir métodos que reciben muchos muchos argumentos, por ejemplo:¿La mejor práctica para pasar muchos argumentos al método?

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2) 
{ 
} 

Cuando me encuentro con este tipo de problemas, a menudo encapsulan argumentos en un mapa.

Map<Object,Object> params = new HashMap<Object,Object>(); 
params.put("objA",ObjA) ; 

...... 

public void doSomething(Map<Object,Object> params) 
{ 
// extracting params 
Object objA = (Object)params.get("objA"); 
...... 
} 

Esta no es una buena práctica, encapsular params en un mapa es totalmente una pérdida de eficiencia. Lo bueno es que, la firma limpia, es fácil agregar otros parámetros con la menor modificación. ¿cuál es la mejor práctica para este tipo de problema?

Respuesta

101

En Effective Java, Capítulo 7 (Métodos), artículo 40 (método de diseño de firmas con cuidado), Bloch escribe:

Existen tres técnicas para acortar las listas de parámetros excesivamente largos:

  • de quiebre el método en múltiples métodos, cada uno de los cuales requiere solo un subconjunto de los parámetros
  • crear clases auxiliares para mantener un grupo de parámetros (generalmente clases de miembros estáticos)
  • adaptar el patrón del constructor desde la construcción del objeto ucción a la invocación del método.

Para obtener más información, lo invito a comprar el libro, realmente lo vale.

+0

¿Qué es "parámetros demasiado largos"? ¿Cuándo podemos decir que un método tiene demasiados parámetros?¿Hay un número específico o un rango? –

+0

@RedM Siempre consideré que más de 3 o 4 parámetros son "demasiado largos" – jtate

+1

@jtate es una elección personal o ¿está siguiendo un documento oficial? –

2

Una buena práctica sería refactorizar. ¿Qué pasa con estos objetos significa que deben pasarse a este método? ¿Deberían estar encapsulados en un solo objeto?

+0

sí, deberían. Por ejemplo, un formulario de búsqueda grande, tiene muchas restricciones y necesidades no relacionadas para la paginación. necesita pasar currentPageNumber, searchCriteria, pageSize ... – Sawyer

4

Puede crear una clase para contener esa información. Sin embargo, tiene que ser lo suficientemente significativo, pero mucho mejor que usar un mapa (OMG).

+0

No creo que sea necesario crear una clase para contener un parámetro de método. – Sawyer

+0

Solo crearía la clase si hubiera varias instancias para pasar los mismos parámetros. Esto indicaría que los parámetros están relacionados y probablemente pertenezcan juntos de todos modos. Si está creando una clase para un solo método, la cura probablemente sea peor que la enfermedad. – tvanfosson

+0

Sí, podría mover params relacionados a un DTO u Objeto de valor. ¿Algunos de los parámetros múltiples son opcionales, es decir, el método principal está sobrecargado con estos parámetros adicionales? En tales casos, siento que es aceptable. – JoseK

61

Usar un mapa con Cadenas mágicas es una mala idea. Pierdes cualquier comprobación de tiempo de compilación, y no está muy claro cuáles son los parámetros requeridos. Debería escribir documentación muy completa para compensarlo. ¿Recordarás en unas pocas semanas qué son esas cuerdas sin mirar el código? ¿Qué pasa si hiciste un error tipográfico? Usar el tipo equivocado? No lo sabrá hasta que ejecute el código.

En lugar de utilizar un modelo. Haz una clase que será un contenedor para todos esos parámetros. De esa forma mantendrás el tipo de seguridad de Java. También puede pasar ese objeto a otros métodos, ponerlo en colecciones, etc.

Por supuesto, si el conjunto de parámetros no se utiliza en otro lugar o se pasa, un modelo dedicado puede ser excesivo. Hay que encontrar un equilibrio, así que usa el sentido común.

2

El uso de un mapa es una forma simple de limpiar la firma de la llamada, pero luego tiene otro problema. Debe buscar dentro del cuerpo del método para ver qué espera el método en ese mapa, cuáles son los nombres clave o qué tipos tienen los valores.

Una manera más limpia sería agrupar todos los parámetros en un bean de objeto, pero eso aún no soluciona el problema por completo.

Lo que tienes aquí es un problema de diseño. Con más de 7 parámetros para un método, comenzará a tener problemas para recordar qué representan y qué orden tienen. Desde aquí, obtendrás muchos errores simplemente llamando al método en un orden de parámetros incorrecto.

Necesita un mejor diseño de la aplicación, no es una buena práctica para enviar muchos parámetros.

5

Hay un patrón llamado Parameter object.

La idea es utilizar un objeto en lugar de todos los parámetros. Ahora, incluso si necesita agregar parámetros más adelante, solo necesita agregarlo al objeto. La interfaz del método permanece igual.

10

Primero, intentaría refactorizar el método. Si usa tantos parámetros, puede ser demasiado largo de cualquier forma. Romperlo mejoraría el código y posiblemente reduciría la cantidad de parámetros para cada método. También es posible que pueda refactorizar toda la operación a su propia clase. En segundo lugar, buscaría otras instancias en las que estoy usando el mismo (o superconjunto) de la misma lista de parámetros. Si tiene varias instancias, entonces probablemente indique que estas propiedades pertenecen juntas. En ese caso, crea una clase para mantener los parámetros y usarlo.Por último, evaluaría si la cantidad de parámetros hace que valga la pena crear un objeto de mapa para mejorar la legibilidad del código. Creo que esta es una decisión personal: hay dolor en cada sentido con esta solución y en la que el punto de equilibrio puede variar. Para seis parámetros, probablemente no lo haría. Para 10 probablemente lo haría (si ninguno de los otros métodos funcionó primero).

12

Se llama "Introducir objeto de parámetro". Si se encuentra pasando la misma lista de parámetros en varios lugares, simplemente cree una clase que los contenga a todos.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2); 
// ... 
doSomething(param); 

Incluso si no encuentras pasando misma lista de parámetros tan a menudo, tan fácil refactorización todavía va a mejorar su legibilidad del código, que es siempre buena. Si miras tu código 3 meses después, será más fácil comprender cuándo necesitas corregir un error o agregar una función.

Es una filosofía general, por supuesto, y como no me ha proporcionado ningún detalle, tampoco puedo darle consejos más detallados. :-)

+0

¿Será la recolección de basura un problema? – rupinderjeet

+0

No si hace que el alcance local del objeto de parámetro en la función de llamador, y si no lo mute. Lo más probable es que se recopile y se reutilice su memoria con bastante rapidez bajo tales circunstancias. – dimitarvp

+0

Imo, también debería tener un 'XXXParameter param = new XXXParameter();' disponible, y luego usar 'XXXParameter.setObjA (objA)'; etc ... – satibel

1

Crea una clase de bean, establece todos los parámetros (método setter) y pasa este objeto bean al método.

0

Si pasa demasiados parámetros, intente refactorizar el método. Tal vez esté haciendo muchas cosas que no se supone que debe hacer. Si ese no es el caso, intente sustituir los parámetros con una sola clase. De esta forma, puede encapsular todo en una única instancia de clase y pasar la instancia y no los parámetros.

21

Si usted tiene muchos parámetros opcionales que puede crear API fluida: sustituir un método único con la cadena de métodos

exportWithParams().datesBetween(date1,date2) 
        .format("xml") 
        .columns("id","name","phone") 
        .table("angry_robots") 
        .invoke(); 

Uso de importación estática puede crear APIs fluidos internos:

... .datesBetween(from(date1).to(date2)) ... 
+2

+1 para la idea bastante creativa, tengo que admitir! –

+1

¿Qué pasa si se requieren todos los parámetros, no es opcional? – Emerald214

+1

También puede tener parámetros predeterminados de esta manera. Además, el [patrón del generador] (http://en.wikipedia.org/wiki/Builder_pattern) está relacionado con las interfaces fluidas. Esta debería ser la respuesta, creo. Además de descomponer un constructor largo en métodos de inicialización más pequeños que son opcionales. –

0
  • Mire su código y vea por qué se pasan todos esos parámetros. A veces es posible refactorizar el método en sí.

  • El uso de un mapa deja su método vulnerable. ¿Qué pasa si alguien que usa su método escribe mal el nombre de un parámetro, o publica una cadena donde su método espera un UDT?

  • Defina un Transfer Object. Le proporcionará una comprobación de tipo al menos; incluso es posible que realice una validación en el punto de uso en lugar de hacerlo dentro de su método.

3

código completo * sugiere un par de cosas:

  • "Limitar el número de parámetros de una rutina a alrededor de siete Siete es un número mágico para la comprensión de la gente." (P 108).
  • "Poner parámetros en orden de entrada-modificar-salida ... Si varias rutinas usan parámetros similares, coloque los parámetros similares en un orden consistente" (p 105).
  • Ponga el estado o las variables de error al final.
  • Como se mencionó tvanfosson, pase solo las partes de las variables estructuradas (objetos) que necesita la rutina. Dicho esto, si está utilizando la mayoría de la variable estructurada en la función, simplemente pase toda la estructura, pero tenga en cuenta que esto promueve el acoplamiento hasta cierto punto.

* Primera edición, sé que debo actualizar. Además, es probable que algunos de estos consejos hayan cambiado desde que se escribió la segunda edición cuando OOP empezaba a ser más popular.

5

Esto a menudo es un problema al construir objetos.

En ese caso, utilice patrón de objeto de generador, funciona bien si tiene una gran lista de parámetros y no siempre los necesita a todos.

También puede adaptarlo a la invocación del método.

También aumenta mucho la legibilidad.

public class BigObject 
{ 
    // public getters 
    // private setters 

    public static class Buider 
    { 
    private A f1; 
    private B f2; 
    private C f3; 
    private D f4; 
    private E f5; 

    public Buider setField1(A f1) { this.f1 = f1; return this; } 
    public Buider setField2(B f2) { this.f2 = f2; return this; } 
    public Buider setField3(C f3) { this.f3 = f3; return this; } 
    public Buider setField4(D f4) { this.f4 = f4; return this; } 
    public Buider setField5(E f5) { this.f5 = f5; return this; } 

    public BigObject build() 
    { 
     BigObject result = new BigObject(); 
     result.setField1(f1); 
     result.setField2(f2); 
     result.setField3(f3); 
     result.setField4(f4); 
     result.setField5(f5); 
     return result; 
    } 
    } 
} 

// Usage: 
BigObject boo = new BigObject.Builder() 
    .setField1(/* whatever */) 
    .setField2(/* whatever */) 
    .setField3(/* whatever */) 
    .setField4(/* whatever */) 
    .setField5(/* whatever */) 
    .build(); 

También puede poner la lógica de verificación en los métodos de configuración de compilación ..() y compilación().

+0

¿Qué recomendarías si muchos de tus campos son 'finales'? Esto es lo principal que me impide escribir funciones de ayuda. Supongo que puedo hacer que los campos sean privados y asegurarme de no modificarlos de forma incorrecta en el código de esa clase, pero espero algo más elegante. – ragerdl

Cuestiones relacionadas