¿Deberían los métodos de una clase llamar a sus propios captadores y establecedores?

53

Donde trabajo veo muchas clases que hacen cosas como esta:

public class ClassThatCallsItsOwnGettersAndSetters {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        setField("value");
        //do stuff
        String localField = getField();
        //do stuff with "localField"
    }
}

Si escribiera esto desde cero, habría escrito lo methodWithLogic()siguiente en su lugar:

public class ClassThatUsesItsOwnFields {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        field = "value";
        //do stuff            
        //do stuff with "field"
    }
}

Siento que cuando la clase llama a sus propios captadores y establecedores, hace que el código sea más difícil de leer. Para mí, casi implica que está ocurriendo una lógica compleja en esa llamada al método, aunque en nuestro caso casi nunca ocurre. Cuando estoy depurando un código desconocido, ¿quién puede decir que el error no es un efecto secundario en ese método? En otras palabras, me hace hacer muchos viajes secundarios en el viaje de entender el código.

¿Hay beneficios para el primer método? ¿Es el primer método realmente mejor?

Daniel Kaplan
fuente
Cuando estoy depurando un código desconocido, ¿quién puede decir que el error no es un efecto secundario en ese método? Su unidad prueba. :)
Joshua Taylor
1
El uso de getters / setters en su código interno le permite crear un punto de interrupción en su código si lo atraviesa con un depurador. También le permite asegurarse de que si algo va mal al configurar / obtener un valor, sabe que es por ese método y no por algún acceso no válido. En general, proporciona un código de mayor consistencia.
callyalater

Respuestas:

46

No diré cuál es mejor o peor, porque eso depende en parte de su situación (en mi opinión). Pero considere que sus captadores y establecedores pueden cambiar la implementación más adelante, y omitirlos omitiría esa lógica.

Por ejemplo, ¿qué sucede si agrega una bandera "sucia" a algunos campos de configuración más adelante? Al llamar a sus configuradores en su código interno, establecerá la bandera sucia sin cambiar ningún otro código. En muchas situaciones esto sería algo bueno.

Matt S
fuente
Pero, ¿qué pasa cuando estás inicializando los datos en el backend y no quieres que se interprete como sucio? Si llamó setField()desde el principio, acaba de introducir un error.
Daniel Kaplan
66
Bien, por eso digo que depende en parte de la situación. Tal vez su inicialización de datos debería omitir los configuradores, pero el otro código lógico no debería. Elija las reglas que sean apropiadas y sígalas.
Matt S
3
@DanielKaplan: el punto es que llamar a getters y setters es en la mayoría de los casos lo correcto, y solo en situaciones específicas no. Sin embargo, en el código del mundo real, cuando cambie una implementación existente de getter / setter más tarde para introducir algunos efectos secundarios intencionales, lo más probable es que tenga que verificar cada llamada al getter o setter dentro de la clase, y también cada acceso directo al campo. Es por eso que debes tratar de mantener tus clases lo más pequeñas posible.
Doc Brown
33

Llamar al setter directamente no es, en sí mismo, un problema. La pregunta realmente debería ser: ¿Por qué nuestro código tiene setters en todas partes?

Las clases mutables son un juego peligroso, particularmente donde la clase misma maneja su propio estado. Considere cuántos de esos setters realmente necesitan existir. ¿Cuántos podrían establecerse en el constructor y luego quedar encapsulados completamente por la clase misma?

Si el campo debe ser configurable externamente, pregúntese si el método con lógica debería estar allí. ¿Es su clase en sí misma solo una clase de datos / estado? ¿Esta clase tiene más de una responsabilidad?

No me malinterpreten, habrá casos en que esto está bien. No estoy diciendo que debas eliminar completamente a los setters en las clases con lógica. Pero estas deberían ser la excepción, no la regla. Y, en esos pocos casos, debería ser obvio a qué acceder directamente.

pdr
fuente
1
Estoy completamente de acuerdo / practico esta línea de pensamiento. También creo que traes a colación un punto que de hecho es más importante que mi pregunta. Dicho esto, no siento que responda directamente a mi pregunta original. A menos que esté diciendo, "en el gran esquema de cosas, su pregunta no importa". Lo que también puede ser cierto :)
Daniel Kaplan
@DanielKaplan: Estoy diciendo exactamente eso. :) Estaba dividido entre la respuesta y el comentario con este, pero realmente no creo que haya una respuesta correcta que no haga la pregunta más grande.
pdr
9

Sí, los métodos de su clase deberían llamar a los captadores y establecedores. El objetivo de escribir getters y setters es la prueba futura. Puede convertir cada propiedad en un campo y exponer directamente los datos a los usuarios de la clase. La razón por la que crea los captadores y establecedores no es necesariamente porque haya una lógica compleja ahora, sino para que la interfaz no se rompa en el futuro si tiene que agregarla.

Miguel
fuente
1
No veo la relación entre su primera oración y el resto de su párrafo. La primera oración dice que una clase debería llamar a sus propios captadores y establecedores. El resto del párrafo explica por qué el código del cliente (es decir, el código que usa la clase) debería usar los captadores y establecedores en lugar de acceder a los campos directamente. Pero no veo cómo esa es la razón por la cual la clase no debería acceder directamente a sus propios campos.
Daniel Kaplan
1
@DanielKaplan Mi punto es que los motivos son uno y el mismo. Que si agrega la lógica de establecimiento más adelante, afecta el código interno tanto (potencialmente) como el externo.
Michael
2
@ Michael: A menudo puede haber buenas razones para que una clase no use sus propios captadores / establecedores. Un escenario común es donde hay muchos establecedores de la forma. someField=newValue; refresh(); Si el método permite establecer múltiples campos, llamar a los establecedores para escribir esos campos causaría operaciones redundantes de "actualización". Escribir todos los campos y luego llamar refresh()una vez puede generar una operación más eficiente y de aspecto más uniforme.
supercat
6

Para responder a sus preguntas en una palabra, sí.

Tener una clase que llame a sus propios captadores y establecedores agrega extensibilidad y proporciona una mejor base para el código futuro.

Digamos que tienes algo como esto:

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        this.year = year;
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

Llamar a los emisores por año y marca actualmente no puede agregar ninguna funcionalidad, pero ¿qué pasa si desea agregar algo como la validación de entrada a los configuradores?

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        if(year > 0)
        {
            this.year = year;
        }
        else
        {
            System.out.println(year + " is not a valid year!");
        }
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}
Zach Latta
fuente
5

Una cosa que no se ha mencionado es que getter y setter (como todos los métodos) son virtuales en Java. Esto agrega otra característica para usarlos siempre en su código. Otro usuario podría extender su clase, sobrescribiendo sus captadores y establecedores. Su clase base estaría usando los datos de la subclase en lugar de los suyos. En un lenguaje donde marca explícitamente funciones virtuales, esto es mucho más útil ya que puede predecir y declarar con qué funciones se puede hacer esto. En Java, esto es algo que siempre debería tener en cuenta. Si es un comportamiento deseado, usarlos en su código es algo bueno, de lo contrario, no tanto.

Jonathan Henson
fuente
3

Si está tratando de lograr algo que proporciona la interfaz pública, utilice los captadores / establecedores.

Sin embargo, como propietario / desarrollador de la clase, se le permite acceder a las secciones privadas de su código (desde dentro de la clase, por supuesto), pero también es responsable de mitigar los peligros.

Entonces, tal vez tenga un getter que itera sobre alguna lista interna, y desea obtener el valor actual sin incrementar el iterador. En este caso, use la variable privada.

public class MyClass
{
    private int i;
    private List<string> list;
    public string getNextString()
    {
        i++;
        return list[i];
    }

    private void getString()
    {
        // Do not increment
        string currentString = list[i];

        // Increment
        string nextString = getNextString();
    }
}
CondiciónRacer
fuente
¿Puedes dar el razonamiento para esto?
Daniel Kaplan
1

Si. Los captadores y los establecedores representan el estado, así que cambie esta pregunta: ¿desea tener en cuenta varias formas de cambiar el estado de un objeto de la misma manera a menos que sea necesario?

Cuantas menos cosas tenga que seguir, mejor; es por eso que los objetos inmutables son más fáciles de manejar.

Considere, no hay nada que le impida tener tanto un campo público como un captador / setter público, pero ¿qué ganaría?

En ocasiones puede ser deseable o incluso necesario acceder directamente al campo por una o más razones, no debe evitar eso si sucede. Pero realmente solo debe hacerlo si hay un beneficio definido al hacerlo.

jmoreno
fuente
El punto de mi pregunta es que solo estoy preguntando sobre el acceso directo de los campos en la misma clase. Ya entiendo el propósito de getters y setters para el código del cliente.
Daniel Kaplan
@DanielKaplan: Entiendo eso, y lo que digo es que, en la medida de lo posible, debes tratarlos de la misma manera.
jmoreno
@DanielKaplan: Podrías tener un setter privado y uno setter público, que tengan exactamente el mismo resultado (todos los efectos secundarios iguales) al menos por el momento, pero ¿qué te ganaría más que el potencial de que las cosas diverjan? La diferencia entre este escenario y lo que describe es que no puede evitar poder acceder al estado fuera de los captadores / establecedores, pero sí puede evitar hacerlo. No complique su código a menos que tenga que hacerlo.
jmoreno
1

Ambos métodos tienen sus casos de uso. Como public setter mantiene el valor del campo (y / o los valores enlazados) consistentes, debe usar setter cuando la lógica de su método no interfiere con esta lógica de coherencia. Si acaba de configurar su "propiedad", use setter. Por otro lado, hay situaciones en las que necesita acceso directo a algunos campos, por ejemplo, operaciones masivas con setter pesado u operaciones en las que el concepto de setter es demasiado simple.

Es su responsabilidad mantener las cosas consistentes. Setter hace esto por definición, pero no puede cubrir casos complejos.

Artur
fuente
1

Yo diría que no..

Si sus captadores / establecedores solo obtienen y configuran (sin inicialización diferida, sin controles, etc.), entonces simplemente use sus variables. Si en el futuro cambias tus captadores / setters, puedes cambiar tu methodWithLogic()(porque es tu clase la que está cambiando) y puedes llamar a un getter / setter en lugar de una asignación directa. Puede documentar esta llamada para el getter / setter (ya que será extraño llamar a un getter / setter cuando el resto del código de su clase está utilizando directamente la variable).

La JVM incorporará llamadas frecuentes a captadores / establecedores. Entonces, nunca es un problema de rendimiento. La ganancia de usar variables es la legibilidad y en mi humilde opinión, iría por ello.

Espero que esto ayude..

Pravin Sonawane
fuente