c++ - una - ¿Está bien renunciar a getters y setters para clases simples?
setter y getter c++ (6)
Estoy haciendo una clase muy simple para representar posiciones en el espacio 3D.
Actualmente, solo estoy permitiendo que el usuario acceda y modifique los valores individuales de X
, Y
y Z
directamente. En otras palabras, son variables de miembros públicos.
template <typename NumericType = double>
struct Position
{
NumericType X, Y, Z;
// Constructors, operators and stuff...
};
El razonamiento detrás de esto es que, debido a que NumericType
es un parámetro de plantilla, no puedo confiar en que haya una manera decente de verificar los valores para la cordura. (¿Cómo sé que el usuario no quiere que una posición se represente con valores negativos?) Por lo tanto, no tiene sentido agregar agregadores o definidores para complicar la interfaz, y el acceso directo debe ser favorecido por su brevedad.
Pos.X = Pos.Y + Pos.Z; // Versus...
Pos.SetX(Pos.GetY() + Pos.GetZ());
¿Es esta una excepción aceptable a la buena práctica? ¿Un futuro mantenedor (hipotético) de mi código me buscará y me pegará en la cara?
El razonamiento detrás de esto es que, debido a que NumericType es un parámetro de plantilla, no puedo confiar en que haya una manera decente de verificar los valores para la cordura. (¿Cómo sé que el usuario no quiere que una posición se represente con valores negativos?)
El lenguaje y los compiladores soportan bien este caso (a través de la especialización).
Por lo tanto, no tiene sentido agregar agregadores o definidores para complicar la interfaz, y el acceso directo debe ser favorecido por su brevedad.
Argumento discutible - ver más arriba.
¿Es esta una excepción aceptable a la buena práctica?
No creo que lo sea. Su pregunta implica que la validación debería existir, pero no vale la pena implementarla / respaldarla porque eligió usar una template
en su implementación y no se especializa en la función de idioma que eligió. Según ese enfoque, la interfaz solo parece ser parcialmente compatible: las implementaciones faltantes solo contaminarán las implementaciones de los clientes.
Está bien para una estructura tan conocida que:
- Puede tener cualquier valor posible, como un int;
- Debería funcionar como un tipo integrado al manipular su valor, por razones de rendimiento.
Sin embargo, si necesita más que un tipo que "solo es un vector 3D", entonces debe envolverlo en otra clase, como miembro privado, que luego expondría x, y y z a través de funciones miembro y funciones adicionales funciones miembro.
La idea detrás de usar getters y setters es poder realizar otro comportamiento que solo establecer un valor. Se recomienda esta práctica porque hay una multitud de cosas que quizás desee adaptar a su clase.
Razones comunes para usar un setter (probablemente hay más):
- Validación : no todos los valores permitidos por el tipo de la variable son válidos para el miembro: se requiere validación antes de la asignación.
- Invariantes : es posible que los campos dependientes deban ajustarse (por ejemplo, cambiar el tamaño de una matriz puede requerir una reasignación, no solo el almacenamiento del nuevo tamaño).
- Enganches : hay trabajo adicional para realizar la asignación antes / después, como la activación de notificaciones (por ejemplo, los observadores / oyentes están registrados en el valor).
- Representación : el campo no se almacena en el formato "publicado" como captadores y definidores. El campo puede que ni siquiera se almacene en el objeto en sí; el valor podría reenviarse a algún otro miembro interno o almacenarse en componentes separados.
Si crees que tu código nunca utilizará o requerirá alguno de los anteriores, entonces escribir los métodos de obtención y configuración por principio no es una buena práctica. Simplemente resulta en el código inflado.
Edit : contrariamente a la creencia popular, es poco probable que el uso de getters y setters te ayude a cambiar la representación interna de la clase a menos que estos cambios sean menores. La presencia de setters para miembros individuales, en particular, hace que este cambio sea muy difícil.
Los captadores y los definidores son realmente solo una opción de diseño importante si obtienen / establecen un valor abstracto que puede haber implementado de varias formas. Pero si su clase es tan directa y los miembros de los datos son tan fundamentales que necesita exponerlos directamente, ¡entonces hágalos públicos! Obtienes un tipo de agregado agradable y barato sin lujos y es completamente auto-documentado.
Si realmente desea que un miembro de datos sea privado, pero aún así puede darle acceso total, simplemente haga que una sola función de acceso se sobrecargue una vez como T & access()
y una vez como const T & access() const
.
Edición: En un proyecto reciente, simplemente usé tuplas para las coordenadas, con funciones de acceso globales. Quizás esto podría ser útil:
template <typename T>
inline T cX(const std::tuple<T,T,T> & t) { return std::get<0>(t); }
typedef std::tuple<double, double, double> coords;
//template <typename T> using coords = std::tuple<T,T,T>; // if I had GCC 4.8
coords c{1.2, -3.4, 5.6};
// Now we can access cX(c), cY(c), cZ(c).
Me tomó un tiempo, pero rastreé esta vieja entrevista de Stroustrup, donde él mismo analiza las estructuras de datos expuestos frente a las clases encapsuladas: http://www.artima.com/intv/goldilocks3.html
En cuanto a los detalles específicos, hay dimensiones a esto que pueden faltar / subestimarse en las respuestas existentes. Los beneficios de la encapsulación aumentan con:
- Re-compilación / dependencia de enlace : código de biblioteca de bajo nivel que es usado por un gran número de aplicaciones, donde esas aplicaciones pueden llevar mucho tiempo y / o ser difíciles de compilar y redistribuir.
- por lo general, es más fácil si la implementación estuvo fuera de línea (lo que puede requerir compromisos de rendimiento y de idioma pImpl), por lo que solo tiene que volver a vincularlo, y aún más fácil si puede implementar nuevas bibliotecas compartidas y simplemente rebotar la aplicación
- a modo de contraste, hay mucho menos beneficio de la encapsulación si el objeto solo se utiliza en la implementación "no
extern
" de una unidad de traducción específica
- estabilidad de la interfaz a pesar de la volatilidad de la implementación : código donde la implementación es más experimental / volátil, pero el requisito de la API se entiende bien
- tenga en cuenta que al tener cuidado, puede ser posible dar acceso directo a las variables miembro al usar
typedef
s para sus tipos, de modo que un objeto proxy pueda sustituirse y admitir un uso idéntico del código de cliente al invocar diferentes implementaciones
- tenga en cuenta que al tener cuidado, puede ser posible dar acceso directo a las variables miembro al usar
Si haces algunas cosas muy fáciles, tu solución podría estar bien.
Si luego te das cuenta de que los cálculos en un sistema de coordenadas esféricas son mucho más fáciles o más rápidos (y necesitas rendimiento), puedes contar con ese golpe.