¿Por qué utilizar captadores y definidores/accesores?
¿Cuál es la ventaja de usar captadores y definidores (que solo obtienen y configuran) en lugar de simplemente usar campos públicos para esas variables?
Si los captadores y definidores alguna vez hacen algo más que simplemente obtener/establecer, puedo resolver esto muy rápidamente, pero no tengo 100% claro cómo:
public String foo;
es peor que:
private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }
Mientras que el primero requiere mucho menos código repetitivo.
En realidad, existen muchas buenas razones para considerar el uso de descriptores de acceso en lugar de exponer directamente los campos de una clase, más allá del argumento de la encapsulación y de facilitar los cambios futuros.
Estas son algunas de las razones que conozco:
- Encapsulación del comportamiento asociado con la obtención o configuración de la propiedad: esto permite agregar funciones adicionales (como la validación) más fácilmente más adelante.
- Ocultar la representación interna de la propiedad mientras se expone una propiedad utilizando una representación alternativa.
- Aislar su interfaz pública del cambio: permitir que la interfaz pública permanezca constante mientras la implementación cambia sin afectar a los consumidores existentes.
- Controlar la semántica de vida útil y administración (eliminación) de la memoria de la propiedad, particularmente importante en entornos de memoria no administrada (como C++ u Objective-C).
- Proporcionar un punto de intercepción de depuración para cuando una propiedad cambia en tiempo de ejecución: depurar cuándo y dónde una propiedad cambió a un valor particular puede ser bastante difícil sin esto en algunos lenguajes.
- Interoperabilidad mejorada con bibliotecas que están diseñadas para operar contra captadores/definidores de propiedades: me vienen a la mente la burla, la serialización y WPF.
- Permitir a los herederos cambiar la semántica de cómo se comporta y expone la propiedad anulando los métodos getter/setter.
- Permitir que el captador/definidor se transmita como expresiones lambda en lugar de valores.
- Los captadores y definidores pueden permitir diferentes niveles de acceso; por ejemplo, la obtención puede ser pública, pero el conjunto podría estar protegido.
Porque dentro de 2 semanas (meses, años) cuando te des cuenta de que tu configurador necesita hacer más que solo establecer el valor, también te darás cuenta de que la propiedad se ha utilizado directamente en otras 238 clases :-)
Un campo público no es peor que un par getter/setter que no hace nada más que devolver el campo y asignarlo. Primero, está claro que (en la mayoría de los idiomas) no existe una diferencia funcional. Cualquier diferencia debe estar en otros factores, como la mantenibilidad o la legibilidad.
Una ventaja que se menciona con frecuencia de los pares getter/setter no lo es. Existe la afirmación de que puede cambiar la implementación y sus clientes no tienen que ser recompilados. Supuestamente, los configuradores le permiten agregar funciones como la validación más adelante y sus clientes ni siquiera necesitan saberlo. Sin embargo, agregar validación a un definidor es un cambio en sus condiciones previas, una violación del contrato anterior , que era, simplemente, "puedes poner cualquier cosa aquí y puedes obtener lo mismo más tarde del captador".
Entonces, ahora que rompiste el contrato, cambiar cada archivo en el código base es algo que deberías querer hacer, no evitar. Si lo evita, estará asumiendo que todo el código asumió que el contrato para esos métodos era diferente.
Si ese no debería haber sido el contrato, entonces la interfaz permitía a los clientes poner el objeto en estados no válidos. Eso es exactamente lo opuesto a la encapsulación. Si ese campo realmente no se podía configurar con ningún valor desde el principio, ¿por qué no estuvo allí la validación desde el principio?
Este mismo argumento se aplica a otras supuestas ventajas de estos pares de captador/establecedor de paso: si luego decide cambiar el valor que se establece, está rompiendo el contrato. Si anula la funcionalidad predeterminada en una clase derivada, más allá de unas pocas modificaciones inofensivas (como el registro u otro comportamiento no observable), está rompiendo el contrato de la clase base. Se trata de una violación del principio de sustituibilidad de Liskov, que se considera uno de los principios de la OO.
Si una clase tiene estos tontos captadores y definidores para cada campo, entonces es una clase que no tiene invariantes de ningún tipo, ni contrato . ¿Es eso realmente un diseño orientado a objetos? Si lo único que tiene la clase son esos captadores y definidores, es solo un poseedor de datos tonto, y los poseedores de datos tontos deberían verse como poseedores de datos tontos:
class Foo {
public:
int DaysLeft;
int ContestantNumber;
};
Agregar pares getter/setter de paso a dicha clase no agrega ningún valor. Otras clases deberían proporcionar operaciones significativas, no sólo operaciones que los campos ya proporcionan. Así es como puedes definir y mantener invariantes útiles.
Cliente : "¿Qué puedo hacer con un objeto de esta clase?"
Diseñador : "Puedes leer y escribir varias variables".
Cliente : "Oh... ¿genial, supongo?"
Hay razones para usar getters y setters, pero si esas razones no existen, hacer pares getter/setter en nombre de falsos dioses de la encapsulación no es algo bueno. Las razones válidas para crear captadores o definidores incluyen las cosas que se mencionan a menudo como los posibles cambios que puede realizar más adelante, como la validación o diferentes representaciones internas. O tal vez los clientes deberían poder leer el valor pero no escribirlo (por ejemplo, leer el tamaño de un diccionario), por lo que un captador simple es una buena opción. Pero esas razones deben estar presentes cuando usted toma la decisión, y no sólo como algo potencial que pueda desear más adelante. Esta es una instancia de YAGNI ( No lo necesitarás ).