c++ - Miembros de datos públicos vs Getters, Setters
(15)
Actualmente estoy trabajando en Qt y así C ++. Estoy teniendo clases que tienen miembros de datos privados y funciones de miembros públicos. Tengo getters y setters públicos para los miembros de datos disponibles en la clase.
Ahora mi pregunta es, si tenemos captadores y establecedores para los miembros de los datos en nuestras clases, ¿cuál es el objetivo de hacer que esos datos sean privados? Acepto que los miembros de datos privados en las clases base suenen lógicos. Pero además de eso, tener miembros privados y también lo hacen sus captores y establecedores no parece ser lógico para mí.
O bien, ¿podemos hacer que todas las variables sean públicas para que no haya necesidad de getters y setters? ¿Es una buena práctica tenerlos? Sé que los miembros privados aseguran la abstracción de los datos, pero tener getters y setters realmente permite el acceso a esas variables con bastante facilidad. Cualquier sugerencia sobre esto es bienvenido.
Además de las preocupaciones sobre la encapsulación (que son motivo suficiente), es muy fácil establecer un punto de interrupción siempre que se establezca / acceda a la variable cuando tenga getters / setters.
Creo que usar getters y setters simplemente para obtener y establecer el valor es inútil. No hay diferencia entre un miembro público y uno privado con dichos métodos. Use getters y setters solo cuando necesite controlar los valores de alguna manera o cuando crea que pueden ser útiles en el futuro (agregar cierta lógica no le hará editar el resto del código).
El uso de getters y setters te permitirá modificar la forma en que le das los valores al usuario.
Considera lo siguiente:
double premium;
double tax;
Luego, escribe el código en todo el lugar usando este valor premium
para obtener la prima:
double myPremium = class.premium;
Sus especificaciones han cambiado y la prima desde el punto de vista del usuario debe ser premium + tax
Deberá modificar en todas partes en que se use ese valor premium
en su código y agregarle tax
.
Si, en cambio, lo implementó como tal:
double premium;
double tax;
double GetPremium(){return premium;};
Todo su código estaría usando GetPremium()
y su cambio de tax
sería una línea:
double premium;
double tax;
double GetPremium(){return premium + tax;};
El valor de retorno también afecta el uso de getters y setters. Es una diferencia obtener el valor de una variable o acceder a la variable de miembro de datos privados. Por valor mantiene la integridad, la referencia o el puntero no tanto.
Getters y Setters existen principalmente para que podamos controlar cómo se obtienen los miembros y cómo se configuran. Getters y setters no existen solo como una forma de acceder a un miembro específico, sino para garantizar que antes de intentar establecer un miembro, que tal vez cumpla ciertas condiciones, o si lo recuperamos, podemos controlar que devolvamos una copia de ese miembro en el caso de un tipo no primitivo. En general, debe probar y usar g / s''ers cuando quiera canalizar cómo se va a interactuar con un miembro de datos, sin ellos el miembro se usará de manera ad hoc.
Getters y Setters le permiten aplicar lógica a la entrada / salida de los miembros privados, por lo tanto, controlar el acceso a los datos (Encapsulación para aquellos que conocen sus términos OO).
Las variables públicas dejan los datos de su clase abiertos al público para una manipulación incontrolada y no validada que casi siempre no es deseable.
También debes pensar en estas cosas a largo plazo. Es posible que no tenga validación ahora (por lo que las variables públicas parecen ser una buena idea), pero existe la posibilidad de que se agreguen más adelante. Sumarlos antes de tiempo deja el marco por lo que hay menos reelaboración de la reputación, sin mencionar que la validación no romperá el código dependiente de esta manera).
Tenga en cuenta, sin embargo, que eso no significa que cada variable privada necesita su propio getter / setter. Neil plantea un buen punto en su ejemplo bancario que a veces los Getters / Setters simplemente no tienen sentido.
Haz públicos los datos. En el caso (algo improbable) de que algún día necesites lógica en el "getter" o "setter", puedes cambiar el tipo de datos a una clase proxy que sobrecarga operator=
y / o operator T
(donde T = sea cual sea tu '' re usando ahora) para implementar la lógica necesaria.
Editar: la idea de que controlar el acceso a los datos constituye una encapsulación es básicamente falsa. La encapsulación consiste en ocultar los detalles de la implementación (¡en general!) Y no controlar el acceso a los datos.
La encapsulación es complementaria a la abstracción: la abstracción se ocupa del comportamiento externo visible del objeto, mientras que la encapsulación trata de ocultar los detalles de cómo se implementa ese comportamiento.
El uso de un getter o setter en realidad reduce el nivel de abstracción y expone la implementación; requiere que el código del cliente tenga en cuenta que esta clase particular implementa lo que lógicamente es "datos" como un par de funciones (getter y setter). El uso de un proxy como he sugerido anteriormente proporciona una encapsulación real : a excepción de un oscuro caso de esquina, oculta por completo el hecho de que, lógicamente, una pieza de datos se implementa a través de un par de funciones.
Por supuesto, esto debe mantenerse en contexto: para algunas clases, "datos" no es una buena abstracción en absoluto. En términos generales, si puede proporcionar operaciones de nivel superior en lugar de datos, es preferible. No obstante, hay clases para las cuales la abstracción más útil es leer y escribir datos, y cuando ese es el caso, los datos (abstraídos) deben hacerse visibles al igual que cualquier otro dato. El hecho de que obtener o establecer el valor puede implicar algo más que la simple copia de bits es un detalle de implementación que debería ocultarse al usuario.
Las razones para usar campos públicos en lugar de getters y setters incluyen:
- No hay valores ilegales
- Se espera que el cliente lo edite.
- Para poder escribir cosas como object.XY = Z.
- Para hacer una fuerte promesa de que el valor es solo un valor y no hay efectos secundarios asociados con él (y tampoco lo estará en el futuro).
Dependiendo del tipo de software en el que trabaje, estos podrían ser casos realmente excepcionales (y si cree que ha encontrado uno, probablemente esté equivocado) o podrían ocurrir todo el tiempo. Realmente depende.
(A partir de diez preguntas sobre la programación basada en el valor ).
Ninguno. Deberías tener métodos que hagan cosas. Si una de esas cosas se corresponde con una variable interna específica, eso es genial, pero no debería haber nada que transmita esto a los usuarios de su clase.
Los datos privados son privados, por lo que puede reemplazar la implementación cuando lo desee (y puede hacer reconstrucciones completas, pero ese es un problema diferente). Una vez que dejas al Genio fuera de la botella, es imposible volver a introducirlo.
EDITAR: Después de un comentario que hice a otra respuesta.
Mi punto aquí es que estás haciendo la pregunta incorrecta. No hay una mejor práctica con respecto al uso de getters / setters o tener miembros públicos. Solo existe lo que es mejor para su objeto específico y cómo modela una cosa específica del mundo real (o algo imaginario, tal vez en el caso del juego).
Personalmente getters / setters son el menor de dos males. Porque una vez que comienzas a hacer getters / setters, las personas dejan de diseñar objetos con un ojo crítico hacia qué datos deberían ser visibles y qué datos no deberían. Con los miembros públicos, es aún peor porque la tendencia es hacer que todo sea público.
En cambio, examine lo que hace el objeto y lo que significa que algo sea ese objeto. Luego crea métodos que proporcionan una interfaz natural en ese objeto. Esa interfaz natural implica exponer algunas propiedades internas usando getters y setters, que así sea. Pero la parte importante es que lo pensaste antes de tiempo y creaste los getters / setters por un motivo justificado para el diseño.
No, ni remotamente es lo mismo.
Hay diferentes niveles de protección / ocultación de la implementación que se pueden lograr mediante diferentes enfoques a una interfaz de clase:
1. Miembro de datos públicos:
- proporciona acceso de lectura y escritura (si no const) al miembro de datos
- expone el hecho de que el objeto de datos existe físicamente y es físicamente un miembro de esta clase (permite crear punteros de tipo puntero a miembro a ese miembro de datos)
- proporciona acceso ilimitado al miembro de datos (le permite a uno crear punteros comunes para el miembro)
2. Un método que devuelve una referencia a un dato (posiblemente a un miembro de datos privado):
- proporciona acceso de lectura y escritura (si no const) a los datos
- expone el hecho de que el objeto de datos existe físicamente pero no expone que es físicamente un miembro de esta clase (no permite crear punteros de tipo puntero a miembro a los datos)
- proporciona acceso ilimitado a los datos (le permite a uno crear punteros comunes)
3. Métodos getter y / o setter (posiblemente accediendo a un miembro de datos privados):
- proporciona acceso de lectura y / o escritura a la propiedad
- no expone el hecho de que el objeto de datos existe físicamente, y mucho menos físicamente presente en esta clase (no permite crear punteros de tipo puntero a miembro a esos datos, ni ningún tipo de punteros para el caso)
- no proporciona acceso lvalue a los datos (no permite crear punteros ordinarios)
El enfoque getter / setter ni siquiera expone el hecho de que la propiedad es implementada por un objeto físico. Es decir, puede que no haya ningún miembro de datos físicos detrás del par getter / setter.
Tomando arriba en la cuenta, es extraño ver a alguien afirmar que un par getter y setter es lo mismo que un miembro público de datos. De hecho, no tienen nada en común.
Por supuesto, hay variaciones de cada enfoque. Un método getter, por ejemplo, puede devolver una referencia constante a los datos, lo que la ubicaría entre (2) y (3).
Si está seguro de que su lógica es simple y que nunca necesita hacer otra cosa al leer / escribir una variable, es mejor mantener los datos públicos. En el caso de C ++, prefiero usar struct en lugar de clase para enfatizar el hecho de que los datos son públicos.
Sin embargo, con frecuencia necesita hacer otras cosas al acceder a los miembros de los datos, o quiere darse la libertad de agregar esta lógica más adelante. En este caso, getters y setters son una buena idea. Su cambio será transparente para los clientes de su código.
Un ejemplo simple de funcionalidad adicional: es posible que desee registrar una cadena de depuración cada vez que acceda a una variable.
Si tiene getters y setters para cada uno de sus elementos de datos, no tiene sentido convertir los datos en privados. Es por eso que tener getters y setters para cada uno de sus elementos de datos es una mala idea. Considere la clase std :: string - (probablemente) tiene UN getter, la función size () y ningún setter.
O considere un objeto de BankAccount
: ¿deberíamos tener SetBalance()
definidor para cambiar el saldo actual? No, la mayoría de los bancos no te agradecerán por implementar tal cosa. En cambio, queremos algo como ApplyTransaction( Transaction & tx )
.
Siempre he pensado que getters y setters son deliberadamente detallados en la mayoría de los lenguajes de programación específicamente para hacerte pensar dos veces antes de usarlos. ¿Por qué tu interlocutor necesita saber sobre el funcionamiento interno de tu clase? Debería ser la pregunta al frente de tu mente .
Sobre una base estrictamente práctica, te sugiero que comiences por hacer todos tus miembros de datos privados, Y hacer que sus captores y decodificadores sean privados. A medida que descubra lo que el resto del mundo (es decir, su "(l) comunidad de usuarios) realmente necesita, puede exponer a los captadores y / o instaladores apropiados, o escribir los accesadores públicos controlados apropiadamente.
También (para el beneficio de Neil), durante el tiempo de depuración, a veces es útil tener un lugar conveniente para colgar impresiones de depuración y otras acciones cuando se lee o escribe un miembro de datos en particular. Con getters y setters, esto es fácil. Con los miembros de datos públicos, es un gran dolor en la parte posterior.
Sugiero que no tenga miembros de datos públicos (a excepción de las estructuras POD). Tampoco recomiendo que tenga getters y setters para todos sus miembros de datos. Más bien, defina una interfaz pública limpia para su clase. Esto puede incluir métodos que obtienen y / o establecen valores de propiedades, y esas propiedades pueden implementarse como variables miembro. Pero no hagas getters y setters para todos tus miembros.
La idea es que separes tu interfaz de tu implementación, permitiéndote modificar la implementación sin que los usuarios de la clase tengan que cambiar su código. Si expone todo a través de getters y setters, no ha mejorado nada sobre el uso de datos públicos.