¿Debemos evitar los objetos personalizados como parámetros?

48

Supongamos que tengo un objeto personalizado, Estudiante :

public class Student{
    public int _id;
    public String name;
    public int age;
    public float score;
}

Y una clase, Ventana , que se usa para mostrar información de un Estudiante :

public class Window{
    public void showInfo(Student student);
}

Parece bastante normal, pero encontré que Window no es fácil de probar individualmente, ya que necesita un objeto real Student para llamar a la función. Entonces trato de modificar showInfo para que no acepte un objeto Student directamente:

public void showInfo(int _id, String name, int age, float score);

para que sea más fácil probar Ventana individualmente:

showInfo(123, "abc", 45, 6.7);

Pero encontré que la versión modificada tiene otros problemas:

  1. Modificar estudiante (por ejemplo: nuevas propiedades) requiere modificar la firma de método de showInfo

  2. Si el Estudiante tuviera muchas propiedades, la firma del método del Estudiante sería muy larga.

Entonces, al usar objetos personalizados como parámetro o aceptar cada propiedad en objetos como parámetro, ¿cuál es la más mantenible?

    
pregunta ggrr 12.05.2016 - 05:12

10 respuestas

131

El uso de un objeto personalizado para agrupar parámetros relacionados es en realidad un patrón recomendado. Como refactorización, se denomina Introduzca el objeto de parámetro .

Tu problema está en otra parte. Primero, el Window genérico no debería saber nada sobre Student. En su lugar, debe tener algún tipo de StudentWindow que sepa que solo muestra Students . En segundo lugar, no hay absolutamente ningún problema en crear la instancia Student para probar StudentWindow siempre y cuando Student no contenga ninguna lógica compleja que complique drásticamente la prueba de StudentWindow . Si tiene esa lógica, entonces se debería preferir hacer de Student una interfaz y burlarse de ella.

    
respondido por el Euphoric 12.05.2016 - 06:02
26

Usted dice que es

  

no es muy fácil de probar individualmente, ya que necesita un objeto real de Student para llamar a la función

Pero puede crear un objeto de estudiante para pasar a su ventana:

showInfo(new Student(123,"abc",45,6.7));

No parece mucho más complejo llamar.

    
respondido por el Tom.Bowen89 12.05.2016 - 10:12
21

En términos sencillos:

  • Lo que usted llama un "objeto personalizado" generalmente se llama simplemente un objeto.
  • No puede evitar pasar objetos como parámetros al diseñar cualquier programa o API no trivial, o utilizar cualquier API o biblioteca no trivial.
  • Es perfectamente correcto pasar objetos como parámetros. Eche un vistazo a la API de Java y verá muchas interfaces que reciben objetos como parámetros.
  • Las clases en las bibliotecas que usas fueron escritas por simples mortales como tú y yo, por lo que las que escribimos no son "personalizadas" , simplemente lo son.

Editar:

Como @ Tom.Bowen89 dice que no es mucho más complejo probar el método showInfo:

showInfo(new Student(8812372,"Peter Parker",16,8.9));
    
respondido por el Tulains Córdova 12.05.2016 - 13:08
3
  1. En el ejemplo de su estudiante, asumo que es trivial llamar al constructor del estudiante para crear un estudiante para pasar a showInfo. Así que no hay problema.
  2. Suponiendo que el Estudiante de ejemplo está deliberadamente trivializado para esta pregunta y es más difícil de construir, entonces podría usar una prueba doble . Hay varias opciones para pruebas de dobles, simulacros, talones, etc. que se mencionan en el artículo de Martin Fowler para elegir.
  3. Si desea que la función showInfo sea más genérica, puede iterarla sobre las variables públicas, o tal vez los accesores públicos del objeto pasados y realizar la lógica de mostrar para todos ellos. Entonces podría pasar cualquier objeto que se ajustara a ese contrato y funcionaría como se esperaba. Este sería un buen lugar para usar una interfaz. Por ejemplo, pase un objeto Showable o ShowInfoable a la función showInfo que puede mostrar no solo la información de los estudiantes, sino también la información de cualquier objeto que implemente la interfaz (obviamente, esas interfaces necesitan mejores nombres dependiendo de qué tan específico o genérico desea que sea el objeto que puede transmitir). ser y lo que un estudiante es una subclase de).
  4. A menudo es más fácil pasar primitivas, y a veces es necesario para el rendimiento, pero cuanto más pueda agrupar conceptos similares, más comprensible será el código. Lo único que hay que tener en cuenta es tratar de no sobrepasarlo y terminar con fizzbuzz empresarial .
respondido por el Encaitar 12.05.2016 - 11:14
3

Steve McConnell en Code Complete abordó este problema, discutiendo los beneficios e inconvenientes de pasar objetos a métodos en lugar de usar propiedades.

Perdóneme si me equivoco con algunos de los detalles, estoy trabajando de memoria ya que ha pasado más de un año desde que tuve acceso al libro:

Llegó a la conclusión de que es mejor que no utilices un objeto, sino que solo envías las propiedades absolutamente necesarias para el método. El método no debería tener que saber nada sobre el objeto fuera de las propiedades que utilizará como parte de sus operaciones. Además, con el tiempo, si se cambia el objeto, esto podría tener consecuencias no deseadas en el método que usa el objeto.

También se refirió a que si terminas con un método que acepta muchos argumentos diferentes, entonces probablemente sea una señal de que el método está haciendo demasiado y debería dividirse en métodos más pequeños.

Sin embargo, a veces, a veces, realmente necesitas muchos parámetros. El ejemplo que da sería de un método que construye una dirección completa, usando muchas propiedades de dirección diferentes (aunque esto podría lograrse utilizando una matriz de cadenas cuando piensas en ello).

    
respondido por el user1666620 12.05.2016 - 12:01
2

Es mucho más fácil escribir y leer pruebas si pasa todo el objeto:

public class AStudentView {
    @Test 
    public void displays_failing_grade_warning_when_a_student_with_a_failing_grade_is_shown() {
        StudentView view = aStudentView();
        view.show(aStudent().withAFailingGrade().build());
        Assert.that(view, displaysFailingGradeWarning());
    }

    private Matcher<StudentView> displaysFailingGradeWarning() {
        ...
    }
}

Para comparación,

view.show(aStudent().withAFailingGrade().build());

la línea podría escribirse, si pasa valores por separado, como:

showAStudentWithAFailingGrade(view);

donde la llamada al método real está enterrada en algún lugar como

private showAStudentWithAFailingGrade(StudentView view) {
    int someId = .....
    String someName = .....
    int someAge = .....
    // why have been I peeking and poking values I don't care about
    decimal aFailingGrade = .....
    view.show(someId, someName, someAge, aFailingGrade);
}

Para llegar al punto en que no se puede poner la llamada al método real en la prueba es una señal de que la API es mala.

    
respondido por el abuzittin gillifirca 12.05.2016 - 12:52
1

Debes pasar lo que tiene sentido, algunas ideas:

Más fácil de probar. Si el objeto (s) necesita ser editado, ¿qué requiere la refactorización mínima? ¿Es útil reutilizar esta función para otros propósitos? ¿Cuál es la menor cantidad de información que necesito para que esta función cumpla su propósito? (Al dividirlo, es posible que le permita reutilizar este código; tenga cuidado de no caer en el agujero de diseño al realizar esta función y, a continuación, obstaculizar todo para usar este objeto exclusivamente).

Todas estas reglas de programación son solo guías para que pienses en la dirección correcta. Simplemente no construya una bestia de código: si no está seguro y necesita continuar, elija una dirección / la suya o una sugerencia aquí, y si llega a un punto en el que piensa "oh, debería haberlo hecho así". forma '- probablemente puedas regresar y refactorizarlo con bastante facilidad. (Por ejemplo, si tiene una clase de Profesor, solo necesita la misma propiedad establecida como Estudiante y usted cambia su función para aceptar cualquier objeto del formulario Persona)

Estaría más inclinado a mantener el objeto principal que se está pasando, porque la forma en que lo codifico explica más fácilmente lo que está haciendo esta función.

    
respondido por el Lilly 12.05.2016 - 22:16
1

Una ruta común para evitar esto es insertar una interfaz entre los dos procesos.

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

interface HasInfo {
    public String getInfo();
}

public class StudentInfo implements HasInfo {
    final Student student;

    public StudentInfo(Student student) {
        this.student = student;
    }

    @Override
    public String getInfo() {
        return student.name;
    }

}

public class Window {

    public void showInfo(HasInfo info) {

    }
}

Esto se puede ensuciar un poco a veces, pero las cosas se ponen un poco más ordenadas en Java si usas una clase interna.

interface HasInfo {
    public String getInfo();
}

public class Student {

    public int id;
    public String name;
    public int age;
    public float score;

    public HasInfo getInfo() {
        return new HasInfo () {
            @Override
            public String getInfo() {
                return name;
            }

        };
    }
}

Luego puedes probar la clase Window simplemente dándole un objeto falso HasInfo .

Sospecho que este es un ejemplo del Decorator Pattern .

Agregado

Parece que hay cierta confusión causada por la simplicidad del código. Aquí hay otro ejemplo que puede demostrar mejor la técnica.

interface Drawable {

    public void Draw(Pane pane);
}

/**
 * Student knows nothing about Window or Drawable.
 */
public class Student {

    public int id;
    public String name;
    public int age;
    public float score;
}

/**
 * DrawsStudents knows about both Students and Drawable (but not Window)
 */
public class DrawsStudents implements Drawable {

    private final Student subject;

    public DrawsStudents(Student subject) {
        this.subject = subject;
    }

    @Override
    public void Draw(Pane pane) {
        // Draw a Student on a Pane
    }

}

/**
 * Window only knows about Drawables.
 */
public class Window {

    public void showInfo(Drawable info) {

    }
}
    
respondido por el OldCurmudgeon 13.05.2016 - 10:39
1

Ya tiene muchas respuestas buenas, pero aquí hay algunas sugerencias más que pueden permitirle ver una solución alternativa:

  • Su ejemplo muestra que un Estudiante (claramente un objeto modelo) se está pasando a una Ventana (aparentemente un objeto de nivel de vista). Un objeto intermediario de controlador o presentador puede ser beneficioso si aún no tiene uno, lo que le permite aislar su interfaz de usuario de su modelo. El controlador / presentador debe proporcionar una interfaz que pueda usarse para reemplazarla para las pruebas de la interfaz de usuario, y debe usar interfaces para referirse a los objetos del modelo y ver los objetos para poder aislarlos de los dos para probar. Es posible que deba proporcionar una forma abstracta de crearlos o cargarlos (por ejemplo, objetos de Factory, objetos de Repository o similares).

  • Transferir partes relevantes de los objetos de tu modelo a un objeto de transferencia de datos es un método útil para interactuar cuando tu modelo se vuelve demasiado complejo.

  • Es posible que su Estudiante infrinja el Principio de Segregación de la Interfaz. Si es así, podría ser beneficioso dividirlo en varias interfaces con las que sea más fácil trabajar.

  • La carga diferida puede facilitar la construcción de gráficos de objetos grandes.

respondido por el Jules 14.05.2016 - 09:16
0

Esta es realmente una pregunta decente. El verdadero problema aquí es el uso del término genérico "objeto", que puede ser un poco ambiguo.

En general, en un lenguaje clásico de POO, el término "objeto" ha llegado a significar "instancia de clase". Las instancias de clase pueden ser bastante pesadas: propiedades públicas y privadas (y otras intermedias), métodos, herencia, dependencias, etc. Realmente no querría usar algo así para simplemente pasar algunas propiedades.

En este caso, está utilizando un objeto como un contenedor que simplemente contiene algunas primitivas. En C ++, los objetos como estos se conocían como structs (y todavía existen en lenguajes como C #). De hecho, las estructuras se diseñaron exactamente para el uso del que habla: agrupaban objetos relacionados y primitivos cuando tenían una relación lógica.

Sin embargo, en los idiomas modernos, realmente no hay diferencia entre una estructura y una clase cuando estás escribiendo el código , por lo que estás bien usando un objeto. (Sin embargo, entre bastidores, hay algunas diferencias que debe tener en cuenta; por ejemplo, una estructura es un tipo de valor, no un tipo de referencia). Básicamente, siempre que mantenga su objeto simple, será fácil. para probar manualmente. Sin embargo, los lenguajes y herramientas modernos le permiten mitigar esto bastante (a través de interfaces, marcos de simulacros, inyección de dependencia, etc.)

    
respondido por el lunchmeat317 13.05.2016 - 01:29

Lea otras preguntas en las etiquetas