java - tutorialspoint - ¿Por qué usar getters y setters/accessors?
polymorphism in java (30)
Utilizamos getters y setters:
- por reutilización
- Realizar validación en etapas posteriores de programación.
Los métodos de getter y setter son interfaces públicas para acceder a miembros de clases privadas.
Mantra de encapsulacion
El mantra de la encapsulación es hacer campos privados y métodos públicos.
Métodos de Getter: Podemos acceder a variables privadas.
Métodos de Setter: Podemos modificar campos privados.
A pesar de que los métodos getter y setter no agregan una nueva funcionalidad, podemos cambiar nuestra mente y volver más tarde para hacer ese método.
- mejor;
- más seguro y
- Más rápido.
En cualquier lugar donde se pueda usar un valor, se puede agregar un método que devuelva ese valor. En lugar de:
int x = 1000 - 500
utilizar
int x = 1000 - class_name.getValue();
En términos sencillos
Supongamos que necesitamos almacenar los detalles de esta Person
. Esta Person
tiene los campos name
, age
y sex
. Hacer esto implica crear métodos para el name
, la age
y el sex
. Ahora, si necesitamos crear otra persona, se hace necesario crear los métodos para el name
, la age
, el sex
nuevamente.
En lugar de hacer esto, podemos crear una class(Person)
bean class(Person)
con los métodos get y set. Así que mañana podemos crear objetos de esta class(Person class)
Bean class(Person class)
cuando sea necesario agregar una nueva persona (ver la figura). Por lo tanto, estamos reutilizando los campos y métodos de la clase de bean, que es mucho mejor.
¿Cuál es la ventaja de usar captadores y definidores, que solo se obtienen y configuran, en lugar de simplemente usar campos públicos para esas variables?
Si los captadores y setters alguna vez hacen algo más que el simple get / set, puedo resolver esto muy rápidamente, pero no estoy 100% claro sobre 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 toma mucho menos código repetitivo.
Bueno, solo quiero agregar que incluso si a veces son necesarias para la encapsulación y la seguridad de sus variables / objetos, si queremos codificar un Programa Orientado a Objetos real, entonces debemos DETENER ABUSO DE LOS ACCESORES , porque a veces dependemos mucho en ellos cuando no es realmente necesario y eso hace casi lo mismo que si pusiéramos las variables públicas.
Debido a que dentro de 2 semanas (meses, años), cuando se dé cuenta de que su configurador necesita hacer más que solo establecer el valor, también se dará cuenta de que la propiedad se ha utilizado directamente en otras 238 clases :-)
Depende de tu idioma. Ha etiquetado este "orientado a objetos" en lugar de "Java", por lo que me gustaría señalar que la respuesta de ChssPly76 depende del idioma. En Python, por ejemplo, no hay razón para usar getters y setters. Si necesita cambiar el comportamiento, puede usar una propiedad, que envuelve al que obtiene y establece el acceso a los atributos básicos. Algo como esto:
class Simple(object):
def _get_value(self):
return self._value -1
def _set_value(self, new_value):
self._value = new_value + 1
def _del_value(self):
self.old_values.append(self._value)
del self._value
value = property(_get_value, _set_value, _del_value)
En un mundo puro orientado a objetos, los captadores y definidores son un anti-patrón terrible . Lee este artículo: Getters / Setters. Mal. Periodo En pocas palabras, alientan a los programadores a pensar en objetos como en estructuras de datos, y este tipo de pensamiento es puramente procedimental (como en COBOL o C). En un lenguaje orientado a objetos, no hay estructuras de datos, sino solo objetos que exponen el comportamiento (¡no atributos / propiedades!)
Puede encontrar más información sobre ellos en la Sección 3.5 de Objetos elegantes (mi libro sobre programación orientada a objetos).
Gracias, eso realmente aclaró mi pensamiento. Ahora aquí hay (casi) 10 (casi) buenas razones para NO utilizar captadores y definidores:
- Cuando se da cuenta de que necesita hacer más que solo establecer y obtener el valor, puede simplemente hacer que el campo sea privado, lo que instantáneamente le indicará a dónde lo ha accedido directamente.
- Cualquier validación que realice allí solo puede ser libre de contexto, cuya validación rara vez es práctica.
- Puede cambiar el valor que se está configurando; esto es una pesadilla absoluta cuando la persona que llama le pasa un valor que [horror horror] quiere que almacene TAL CUAL.
- Puede ocultar la representación interna, es fantástico, por lo que se asegura de que todas estas operaciones sean simétricas, ¿no?
- Ha aislado su interfaz pública de los cambios en las hojas. Si estaba diseñando una interfaz y no estaba seguro de si el acceso directo a algo estaba bien, entonces debería haber seguido diseñando.
- Algunas bibliotecas esperan esto, pero no muchos: la reflexión, la serialización, los objetos simulados funcionan bien en los campos públicos.
- Al heredar esta clase, puede anular la funcionalidad predeterminada; en otras palabras, REALMENTE puede confundir a las personas que llaman, no solo ocultando la implementación sino que también la hace inconsistente.
Los últimos tres que acabo de dejar (N / A o D / C) ...
Hay muchas razones. Mi favorito es cuando necesita cambiar el comportamiento o regular lo que puede establecer en una variable. Por ejemplo, digamos que tenía un método setSpeed (int speed). Pero quieres que solo puedas establecer una velocidad máxima de 100. Harías algo como:
public void setSpeed(int speed) {
if ( speed > 100 ) {
this.speed = 100;
} else {
this.speed = speed;
}
}
Ahora, ¿qué pasa si EN TODAS PARTES de su código estaba usando el campo público y luego se dio cuenta de que necesita el requisito anterior? Diviértete buscando todos los usos del campo público en lugar de solo modificar tu configurador.
Mis 2 centavos :)
Mucha gente habla acerca de las ventajas de los que consiguen y los que ponen, pero quiero jugar a ser el defensor del diablo. En este momento estoy depurando un programa muy grande en el que los programadores decidieron hacer todo lo que tiene que hacer y configurar. Eso puede parecer agradable, pero es una pesadilla de ingeniería inversa.
Digamos que estás viendo cientos de líneas de código y te encuentras con esto:
person.name = "Joe";
Es un código maravillosamente simple hasta que te das cuenta de que es un setter. Ahora, sigue ese setter y encuentra que también establece person.firstName, person.lastName, person.isHuman, person.hasReallyCommonFirstName, y llama a person.update (), que envía una consulta a la base de datos, etc. Oh, eso es donde estaba ocurriendo su pérdida de memoria.
La comprensión de una pieza de código local a primera vista es una propiedad importante de buena legibilidad que los captadores y definidores tienden a romper. Es por eso que trato de evitarlos cuando puedo, y minimizar lo que hacen cuando los uso.
No utilice los configuradores de captadores a menos que sea necesario para su entrega actual. Es decir, no piense demasiado en lo que sucedería en el futuro, si se cambia algo, es una solicitud de cambio en la mayoría de las aplicaciones de producción, sistemas.
Piense simple, fácil, agregue complejidad cuando sea necesario.
No aprovecharía la ignorancia de los dueños de negocios de los conocimientos técnicos profundos, simplemente porque creo que es correcto o que me gusta el enfoque.
Tengo un sistema masivo escrito sin configuradores de captadores solo con modificadores de acceso y algunos métodos para validar y realizar la lógica de biz. Si es absolutamente necesario el. Usa cualquier cosa.
Pasé bastante tiempo pensando en esto para el caso de Java, y creo que las razones reales son:
- Código a la interfaz, no a la implementación.
- Las interfaces solo especifican métodos, no campos
En otras palabras, la única manera de especificar un campo en una interfaz es proporcionar un método para escribir un nuevo valor y un método para leer el valor actual.
Esos métodos son el infame getter y setter ....
Puede ser útil para la carga perezosa. Digamos que el objeto en cuestión está almacenado en una base de datos, y no quiere ir a buscarlo a menos que lo necesite. Si el objeto es recuperado por un captador, entonces el objeto interno puede ser nulo hasta que alguien lo solicite, entonces puede obtenerlo en la primera llamada al captador.
Recibí una clase de página base en un proyecto que me fue entregado y que cargaba algunos datos de un par de llamadas a servicios web diferentes, pero los datos de esas llamadas a servicios web no siempre se usaban en todas las páginas secundarias. Los servicios web, para todos los beneficios, promueven nuevas definiciones de "lento", por lo que no desea hacer una llamada de servicio web si no tiene que hacerlo.
Pasé de los campos públicos a los buscadores, y ahora los captadores revisan el caché y, si no está allí, llaman al servicio web. Así que con un poco de ajuste, se evitaron muchas llamadas de servicio web.
Así que el que lo consigue me evita intentar averiguar, en cada página secundaria, lo que necesitaré. Si lo necesito, llamo al captador y me lo va a buscar si aún no lo tengo.
protected YourType _yourName = null;
public YourType YourName{
get
{
if (_yourName == null)
{
_yourName = new YourType();
return _yourName;
}
}
}
Sé que es un poco tarde, pero creo que hay algunas personas que están interesadas en el rendimiento.
He hecho una pequeña prueba de rendimiento. Escribí una clase "NumberHolder" que, bueno, tiene un entero. Puede leer ese Integer utilizando el método getter anInstance.getNumber()
o accediendo directamente al número utilizando anInstance.number
. Mi programa lee el número 1,000,000,000 veces, a través de ambas formas. Ese proceso se repite cinco veces y se imprime el tiempo. Tengo el siguiente resultado:
Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms
(El tiempo 1 es el camino directo, el tiempo 2 es el captador)
Usted ve, el getter es (casi) siempre un poco más rápido. Luego lo intenté con diferentes números de ciclos. En lugar de 1 millón, usé 10 millones y 0.1 millones. Los resultados:
10 millones de ciclos:
Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms
Con 10 millones de ciclos, los tiempos son casi los mismos. Aquí hay 100 mil (0.1 millones) ciclos:
Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms
También con diferentes cantidades de ciclos, el getter es un poco más rápido que el modo regular. espero que esto te ayude.
Un campo público no es peor que un par getter / setter que no hace nada, excepto devolver el campo y asignarlo. Primero, está claro que (en la mayoría de los idiomas) no hay diferencia funcional. Cualquier diferencia debe estar en otros factores, como facilidad de mantenimiento o legibilidad.
Una ventaja mencionada a menudo de los pares getter / setter, no lo es. Existe la afirmación de que puede cambiar la implementación y que sus clientes no tienen que volver a compilarse. Supuestamente, los configuradores le permiten agregar funcionalidades como la validación más adelante y sus clientes ni siquiera necesitan saberlo. Sin embargo, agregar la validación a un colocador es un cambio a sus condiciones previas, una violación del contrato anterior , que era, simplemente, "puede poner cualquier cosa aquí, y puede obtener esa misma cosa más tarde de quien la recibe".
Por lo tanto, ahora que rompió el contrato, cambiar cada archivo en el código base es algo que debe hacer, no evitar. Si lo evita, está 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 contrario de la encapsulación. Si ese campo realmente no se puede establecer en nada desde el principio, ¿por qué no fue la validación desde el principio?
Este mismo argumento se aplica a otras supuestas ventajas de estos pares de getter / setter de transferencia: si más tarde decide cambiar el valor que se está estableciendo, está rompiendo el contrato. Si reemplaza la funcionalidad predeterminada en una clase derivada, de una manera que va más allá de algunas modificaciones inofensivas (como el registro u otro comportamiento no observable), está incumpliendo el contrato de la clase base. Esa es una violación del Principio de Sustituibilidad de Liskov, que se considera uno de los principios de OO.
Si una clase tiene estos captadores y organizadores tontos para cada campo, entonces es una clase que no tiene invariantes de ningún tipo, ningún contrato . ¿Es eso realmente un diseño orientado a objetos? Si todo lo que la clase tiene son esos captadores y definidores, es solo un titular de datos tontos, y los titulares de datos tontos deben parecer titulares de datos tontos:
class Foo {
public:
int DaysLeft;
int ContestantNumber;
};
Agregar pares de getter / setter a esta clase no agrega ningún valor. Otras clases deben proporcionar operaciones significativas, no solo 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 ... bien, supongo?"
Hay razones para usar captadores y definidores, pero si esas razones no existen, hacer pares de captador / definidor en nombre de falsos dioses de encapsulación no es algo bueno. Las razones válidas para hacer captadores o definidores incluyen las cosas que a menudo se mencionan como los posibles cambios que puede realizar más adelante, como la validación o diferentes representaciones internas. O tal vez el valor debería ser legible para los clientes pero no escribible (por ejemplo, leer el tamaño de un diccionario), por lo que un simple captador es una buena opción. Pero esas razones deberían estar ahí cuando haga la elección, y no solo como una cosa potencial que puede desear más adelante. Esta es una instancia de YAGNI ( No lo vas a necesitar ).
Una ventaja de los accesores y mutadores es que puede realizar la validación.
Por ejemplo, si foo
era público, fácilmente podría establecerlo en null
y luego otra persona podría intentar llamar a un método en el objeto. ¡Pero ya no está allí! Con un método setFoo
, podría asegurarme de que foo
nunca se configuró en null
.
Los accesores y los mutadores también permiten la encapsulación: si no se supone que se debe ver el valor una vez que se establece (tal vez se establezca en el constructor y luego se use por métodos, pero nunca se deba cambiar), nadie lo verá nunca. Pero si puede permitir que otras clases lo vean o lo cambien, puede proporcionar el acceso y / o mutador adecuado.
En realidad, hay muchas buenas razones para considerar el uso de accesores en lugar de exponer directamente los campos de una clase, más allá del argumento de la encapsulación y facilitar cambios futuros.
Estas son algunas de las razones que conozco:
- Encapsulamiento del comportamiento asociado con la obtención o configuración de la propiedad: esto permite que se agreguen más fácilmente funcionalidades adicionales (como la validación) 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, permitiendo que la interfaz pública permanezca constante mientras que la implementación cambia sin afectar a los consumidores existentes.
- Controlar la vida útil y la semántica de la administración de la memoria (disposición) de la propiedad, especialmente importante en entornos de memoria no administrada (como C ++ o 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 cambiada a un valor particular puede ser bastante difícil sin esto en algunos idiomas.
- Interoperabilidad mejorada con las bibliotecas que están diseñadas para operar en contra de los que obtienen / establecen propiedades: se trata de una burla, una serialización y WPF.
- Permitir que los herederos cambien la semántica de cómo se comporta y se expone la propiedad al anular los métodos de obtención / establecimiento.
- Permitir que el getter / setter se transmita como expresiones lambda en lugar de valores.
- Los captadores y los definidores pueden permitir diferentes niveles de acceso, por ejemplo, la obtención puede ser pública, pero el conjunto podría estar protegido.
Getters y setters se utilizan para implementar dos de los aspectos fundamentales de la Programación Orientada a Objetos que son:
- Abstracción
- Encapsulacion
Supongamos que tenemos una clase de empleado:
package com.highmark.productConfig.types;
public class Employee {
private String firstName;
private String middleName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName(){
return this.getFirstName() + this.getMiddleName() + this.getLastName();
}
}
Aquí, los detalles de implementación de Nombre completo están ocultos para el usuario y no son accesibles directamente para el usuario, a diferencia de un atributo público.
Debes usar captadores y definidores cuando:
- Estás tratando con algo que es conceptualmente un atributo, pero:
- Su idioma no tiene propiedades (o algún mecanismo similar, como las trazas variables de Tcl), o
- El soporte de propiedad de su idioma no es suficiente para este caso de uso, o
- Las convenciones idiomáticas de su idioma (o, a veces, de su marco) alientan a los buscadores o configuradores para este caso de uso.
Así que esto rara vez es una pregunta general de OO; es una pregunta específica del idioma, con diferentes respuestas para diferentes idiomas (y diferentes casos de uso).
Desde el punto de vista de la teoría OO, los captadores y los instaladores son inútiles. La interfaz de su clase es lo que hace, no lo que es su estado. (Si no, ha escrito la clase incorrecta). En casos muy simples, lo que hace una clase es simplemente, por ejemplo, representar un punto en coordenadas rectangulares, * los atributos son parte de la interfaz; los captadores y los instaladores simplemente nublan eso. Pero en todos los casos, excepto en casos muy simples, ni los atributos ni los captadores y definidores forman parte de la interfaz.
Dicho de otra manera: si crees que los consumidores de tu clase ni siquiera deberían saber que tienes un spam
atributo, mucho menos poder cambiarlo sin querer, entonces darles un set_spam
método es lo último que quieres hacer.
* Incluso para esa clase simple, es posible que no necesariamente desee permitir la configuración de los valores x
y y
. Si esto es realmente una clase, ¿no debería tener métodos como translate
, rotate
, etc.? Si es solo una clase porque su idioma no tiene registros / estructuras / tuplas con nombre, entonces esto no es realmente una cuestión de OO ...
Pero nadie está haciendo diseño general de OO. Están haciendo diseño e implementación, en un lenguaje específico. Y en algunos idiomas, los captadores y los instaladores están lejos de ser inútiles.
Si su idioma no tiene propiedades, entonces la única manera de representar algo que es conceptualmente un atributo, pero en realidad se calcula, o valida, etc., es a través de captadores y definidores.
Incluso si su idioma tiene propiedades, puede haber casos en que sean insuficientes o inapropiados. Por ejemplo, si desea permitir que las subclases controlen la semántica de un atributo, en idiomas sin acceso dinámico, una subclase no puede sustituir una propiedad computada por un atributo.
En cuanto al "¿qué pasa si quiero cambiar mi implementación más tarde?" Pregunta (que se repite varias veces con palabras diferentes tanto en la pregunta del OP como en la respuesta aceptada): si realmente es un cambio de implementación puro, y comenzó con un atributo, puede cambiarlo a una propiedad sin afectar la interfaz. A menos que, por supuesto, su idioma no lo admita. Así que este es realmente el mismo caso de nuevo.
Además, es importante seguir las expresiones idiomáticas del lenguaje (o marco) que está utilizando. Si escribe un código hermoso de estilo Ruby en C #, cualquier desarrollador de C # con experiencia que no sea usted tendrá problemas para leerlo, y eso es malo. Algunos idiomas tienen culturas más fuertes en torno a sus convenciones que otros, y puede que no sea una coincidencia que Java y Python, que se encuentran en extremos opuestos del espectro de cómo son los captadores idiomáticos, tengan dos de las culturas más fuertes.
Más allá de los lectores humanos, habrá bibliotecas y herramientas que esperan que usted siga las convenciones y haga su vida más difícil si no lo hace. Conectar los widgets de Interface Builder a cualquier cosa que no sean las propiedades de ObjC, o usar ciertas bibliotecas de simulacros de Java sin interlocutores, solo hace que tu vida sea más difícil. Si las herramientas son importantes para usted, no las pelee.
Un aspecto que me faltó en las respuestas hasta ahora, la especificación de acceso:
- para los miembros, solo tiene una especificación de acceso para configurar y obtener
- para los configuradores y captadores, puede ajustarlos y definirlos por separado
Además, esto es para "a prueba de futuro" su clase. En particular, cambiar de un campo a una propiedad es una ruptura ABI, por lo que si decide más adelante que necesita más lógica que solo "establecer / obtener el campo", entonces necesita romper ABI, que por supuesto crea problemas para cualquier cosa otra cosa ya compilada contra tu clase.
Desde el punto de vista del diseño de orientación de objetos, ambas alternativas pueden dañar el mantenimiento del código al debilitar la encapsulación de las clases. Para una discusión puede consultar este excelente artículo: http://typicalprogrammer.com/?p=23
EDITAR: Respondí esta pregunta porque hay muchas personas que están aprendiendo programación preguntando esto, y la mayoría de las respuestas son muy competentes técnicamente, pero no son tan fáciles de entender si eres un novato. Todos éramos novatos, así que pensé en probar una respuesta más amistosa para los novatos.
Los dos principales son el polimorfismo y la validación. Incluso si es sólo una estructura de datos estúpida.
Digamos que tenemos esta clase simple:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Una clase muy simple que contiene la cantidad de líquido que contiene y su capacidad (en mililitros).
Que pasa cuando hago:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacity = 1000;
Bueno, no esperarías que eso funcione, ¿verdad? Quieres que haya algún tipo de control de cordura. Y peor aún, ¿y si nunca especificaba la capacidad máxima? Oh cielos, tenemos un problema.
Pero también hay otro problema. ¿Y si las botellas fueran solo un tipo de envase? ¿Qué pasaría si tuviéramos varios contenedores, todos con capacidades y cantidades de líquido llenos? Si pudiéramos crear una interfaz, podríamos dejar que el resto de nuestro programa la acepte, y las botellas, bidones y todo tipo de cosas funcionarán de manera intercambiable. ¿No sería eso mejor? Dado que las interfaces requieren métodos, esto también es bueno.
Terminaríamos con algo como:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
public void setCapcityMl(int capacityMl);
}
¡Genial! Y ahora solo cambiamos de botella a esto:
public class Bottle extends LiquidContainer {
private int capacityMl;
private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {
this.capacityMl = capacityMl;
this.amountFilledMl = amountFilledMl;
checkNotOverFlow();
}
public int getAmountMl() {
return amountFilledMl;
}
public void setAmountMl(int amountMl) {
this.amountFilled = amountMl;
checkNotOverFlow();
}
public int getCapacityMl() {
return capacityMl;
public void setCapcityMl(int capacityMl) {
this.capacityMl = capacityMl;
checkNotOverFlow();
}
private void checkNotOverFlow() {
if(amountOfWaterMl > capacityMl) {
throw new BottleOverflowException();
}
}
Dejaré la definición de BottleOverflowException como un ejercicio para el lector.
Ahora note cuánto más robusto es esto. Podemos tratar con cualquier tipo de contenedor en nuestro código ahora aceptando LiquidContainer en lugar de Bottle. Y cómo estas botellas tratan con este tipo de cosas pueden ser diferentes. Puede tener botellas que escriben su estado en disco cuando cambia, o botellas que guardan en bases de datos SQL o GNU sabe qué más.
Y todos estos pueden tener diferentes maneras de manejar varias whoopsies. La botella solo verifica y si está desbordada lanza una excepción RuntimeException. Pero eso podría ser lo incorrecto de hacer. (Hay una discusión útil sobre el manejo de errores, pero lo mantengo muy simple aquí a propósito. La gente en los comentarios probablemente señalará los defectos de este enfoque simplista.
Y sí, parece que pasamos de una idea muy simple a obtener respuestas mucho mejores rápidamente.
También está la tercera cosa que no todos han abordado: los captadores y los configuradores utilizan las llamadas a métodos. Eso significa que se ven como los métodos normales en cualquier otro lugar. En lugar de tener una sintaxis específica extraña para DTO y esas cosas, tienes lo mismo en todas partes.
El código evoluciona . private
es ideal para cuando necesita protección de miembros de datos . Eventualmente, todas las clases deben ser una especie de "miniprogramas" que tienen una interfaz bien definida que no se puede limitar a los internos .
Dicho esto, el desarrollo de software no consiste en establecer esa versión final de la clase como si estuvieras presionando una estatua de hierro fundido en el primer intento. Mientras trabajas con él, el código es más como arcilla. Evoluciona a medida que lo desarrolla y aprende más sobre el dominio del problema que está resolviendo. Durante el desarrollo, las clases pueden interactuar entre sí de lo que deberían (dependencia que planea descartar), fusionarse o separarse. Así que creo que el debate se reduce a las personas que no quieren escribir religiosamente
int getVar() const { return var ; }
Así que tienes:
doSomething( obj->getVar() ) ;
En lugar de
doSomething( obj->var ) ;
No solo es getVar()
visualmente ruidoso, sino que da a esta ilusión que gettingVar()
es de alguna manera un proceso más complejo de lo que realmente es. La manera en que usted (como el escritor de la clase) considera que la santidad var
es particularmente confusa para un usuario de su clase si tiene un activador de passthru - entonces parece que está poniendo estas puertas para "proteger" algo que insiste que es valioso, (la santidad de var
), pero aun así, usted acepta que var
la protección no vale mucho por la capacidad de cualquiera para entrar y set
var
por el valor que desee, sin que usted incluso se asuste en lo que está haciendo.
Así que programo de la siguiente manera (asumiendo un enfoque de tipo "ágil", es decir, cuando escribo el código sin saber exactamente lo que hará / no tengo tiempo o experiencia para planificar un conjunto de interfaces de estilo de cascada elaborado):
1) Comience con todos los miembros públicos para objetos básicos con datos y comportamiento. Esta es la razón por la que en todo mi código de "ejemplo" de C ++ notará que lo uso en struct
lugar de en class
todas partes.
2) Cuando el comportamiento interno de un objeto para un miembro de datos se vuelve lo suficientemente complejo (por ejemplo, le gusta mantener un interno std::list
en algún tipo de orden), se escriben las funciones de tipo de acceso. Debido a que estoy programando por mi cuenta, no siempre configuro al miembro de private
inmediato, pero en algún lugar de la evolución de la clase, el miembro será "promovido" a uno protected
u otro private
.
3) Las clases que están completamente desarrolladas y tienen reglas estrictas sobre sus aspectos internos (es decir , saben exactamente lo que están haciendo, y usted no debe "joder" (término técnico) con sus class
componentes internos) reciben la designación, miembros privados predeterminados, y solo unos pocos miembros seleccionados pueden ser public
.
Encuentro que este enfoque me permite evitar sentarme allí y escribir religiosamente a los getters / setters cuando muchos miembros de los datos se migran, cambian de lugar, etc. durante las etapas iniciales de la evolución de una clase.
En idiomas que no admiten "propiedades" (C ++, Java) o que requieren la recompilación de clientes al cambiar campos a propiedades (C #), es más fácil modificar los métodos de obtención / configuración. Por ejemplo, agregar lógica de validación a un método setFoo no requerirá cambiar la interfaz pública de una clase.
En los idiomas que admiten propiedades "reales" (Python, Ruby, ¿quizás Smalltalk?) No hay ningún punto para obtener / establecer métodos.
En un lenguaje orientado a objetos, los métodos y sus modificadores de acceso declaran la interfaz para ese objeto. Entre el constructor y los métodos de acceso y mutación es posible que el desarrollador controle el acceso al estado interno de un objeto. Si las variables simplemente se declaran públicas, no hay manera de regular ese acceso. Y cuando estamos usando configuradores podemos restringir al usuario para la entrada que necesitamos. La media de la misma variable vendrá a través de un canal adecuado y nosotros lo predefinimos. Así que es más seguro usar setters.
Hay una buena razón para considerar el uso de accesores si no hay herencia de propiedad. Vea el siguiente ejemplo:
public class TestPropertyOverride {
public static class A {
public int i = 0;
public void add() {
i++;
}
public int getI() {
return i;
}
}
public static class B extends A {
public int i = 2;
@Override
public void add() {
i = i + 2;
}
@Override
public int getI() {
return i;
}
}
public static void main(String[] args) {
A a = new B();
System.out.println(a.i);
a.add();
System.out.println(a.i);
System.out.println(a.getI());
}
}
Salida:
0
0
4
Los métodos de getter y setter son métodos de acceso, lo que significa que generalmente son una interfaz pública para cambiar miembros de clase privada. Utiliza los métodos getter y setter para definir una propiedad. Puede acceder a los métodos de captador y definidor como propiedades fuera de la clase, aunque los defina dentro de la clase como métodos. Esas propiedades fuera de la clase pueden tener un nombre diferente al nombre de la propiedad en la clase.
Hay algunas ventajas de usar los métodos de obtención y establecimiento, como la capacidad de permitirle crear miembros con una funcionalidad sofisticada a la que puede acceder como propiedades. También te permiten crear propiedades de solo lectura y solo escritura.
Aunque los métodos de obtención y configuración son útiles, debe tener cuidado de no abusar de ellos porque, entre otras cuestiones, pueden dificultar el mantenimiento del código en ciertas situaciones. Además, brindan acceso a la implementación de su clase, como miembros públicos. La práctica de la programación orientada a objetos desalienta el acceso directo a las propiedades dentro de una clase.
Al escribir clases, siempre se recomienda que las variables de instancia sean privadas y agregue los métodos de obtención y configuración en consecuencia. Esto se debe a que en varias ocasiones es posible que no desee que los usuarios cambien ciertas variables dentro de sus clases. Por ejemplo, si tiene un método estático privado que rastrea el número de instancias creadas para una clase específica, no desea que un usuario modifique ese contador utilizando el código. Solo la declaración del constructor debe incrementar esa variable cada vez que se llama. En esta situación, puede crear una variable de instancia privada y permitir un método de obtención solo para la variable de contador, lo que significa que los usuarios pueden recuperar el valor actual solo mediante el método de obtención y no podrán establecer nuevos valores utilizando el método setter.Crear un captador sin un definidor es una forma simple de hacer que ciertas variables en su clase sean de solo lectura.
Otro uso (en los idiomas que admiten propiedades) es que los establecedores y captadores pueden implicar que una operación no es trivial. Normalmente, usted quiere evitar hacer cualquier cosa que sea computacionalmente costosa en una propiedad.
Solo me gustaría lanzar la idea de anotación: @getter y @setter. Con @getter, deberías poder obj = class.field pero no class.field = obj. Con @setter, viceversa. Con @getter y @setter deberías poder hacer ambas cosas. Esto preservaría la encapsulación y reduciría el tiempo al no llamar a métodos triviales en tiempo de ejecución.
Una ventaja relativamente moderna de los getters / setters es que facilita la búsqueda de códigos en los editores de códigos etiquetados (indexados). Por ejemplo, si desea ver quién establece un miembro, puede abrir la jerarquía de llamadas del configurador.
Por otro lado, si el miembro es público, las herramientas no permiten filtrar el acceso de lectura / escritura al miembro. Así que tienes que andar con todos los usos del miembro.
Uno de los principios básicos del diseño OO: ¡la encapsulación!
Le brinda muchos beneficios, uno de los cuales es que puede cambiar la implementación de getter / setter entre bastidores, pero cualquier consumidor de ese valor continuará trabajando mientras el tipo de datos permanezca igual.