En primer lugar, seamos claros en el paradigma.
- Estructuras de datos -> un diseño de memoria que puede ser atravesado y manipulado por funciones adecuadamente informadas.
- Objetos -> un módulo autónomo que oculta su implementación y proporciona una interfaz a través de la cual se puede comunicar.
¿Dónde es útil un getter / setter?
¿Son útiles los getters / setters en las estructuras de datos? No.
Una estructura de datos es una especificación de diseño de memoria que es común y manipulada por una familia de funciones.
En general, cualquier función nueva y antigua puede aparecer y manipular una estructura de datos, si lo hace de una manera que las otras funciones aún puedan entenderla, entonces la función se une a la familia. De lo contrario, es una función maliciosa y una fuente de errores.
No me malinterpreten, podría haber varias familias de funciones que luchan sobre esa estructura de datos con soplones, revestimientos y agentes dobles en todas partes. Está bien cuando cada uno tiene su propia estructura de datos con la que jugar, pero cuando la comparten ... imagínense que varias familias del crimen no están de acuerdo con la política, puede convertirse en un desastre realmente rápido.
Dado el desorden que pueden lograr las familias de funciones extendidas, ¿hay alguna manera de codificar la estructura de datos para que las funciones no autorizadas no lo estropeen todo? Sí, se llaman objetos.
¿Son útiles los getters / setters en los objetos? No.
El objetivo de envolver una estructura de datos en un objeto era asegurar que no pudieran existir funciones deshonestas. Si la función quería unirse a la familia, primero tenía que ser examinada a fondo y luego convertirse en parte del objeto.
El objetivo / propósito de un getter y un setter es permitir que las funciones fuera del objeto alteren la disposición de la memoria del objeto directamente. Eso suena como una puerta abierta para permitir en pícaros ...
The Edge Case
Hay dos situaciones en las que un captador / colocador público tiene sentido.
- Una parte de la estructura de datos dentro del objeto es administrada por el objeto, pero no controlada por el objeto.
- Una interfaz que describe una abstracción de alto nivel de una estructura de datos donde se espera que algunos elementos no controlen el objeto de implementación.
Los contenedores y las interfaces de contenedor son ejemplos perfectos de estas dos situaciones. El contenedor gestiona las estructuras de datos (lista enlazada, mapa, árbol) internamente, pero controla el elemento específico a todos y cada uno. La interfaz resume esto e ignora la implementación por completo y describe solo las expectativas.
Desafortunadamente, muchas implementaciones se equivocan y definen la interfaz de este tipo de objetos para dar acceso directo al objeto real. Algo como:
interface Container<T>
{
typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
TRef item(int index);
}
Esto está descompuesto. Las implementaciones de Container deben entregar explícitamente el control de sus componentes internos a quien los use. Todavía no he visto un lenguaje de valor mutable en el que esto esté bien (los lenguajes con semántica de valor inmutable están bien por definición desde una perspectiva de corrupción de datos, pero no necesariamente desde una perspectiva de espionaje de datos).
Puede mejorar / corregir los captadores / establecedores utilizando solo semántica de copia o utilizando un proxy:
interface Proxy<T>
{
operator T(); //<returns a copy
... operator ->(); //<permits a function call to be forwarded to an element
Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}
interface Container<T>
{
Proxy<T> item(int index);
T item(int index); //<When T is a copy of the original value.
void item(int index, T new_value); //<where new_value is used to replace the old value
}
Podría decirse que una función deshonesta aún podría jugar al caos aquí (con suficiente esfuerzo, la mayoría de las cosas son posibles), pero la semántica de copia y / o proxy reduce la posibilidad de una serie de errores.
- rebosar
- desbordamiento
- las interacciones con el subelemento son verificadas por tipo / verificables por tipo (en tipos de idiomas perdidos esto es una bendición)
- El elemento real puede o no ser residente de memoria.
Getters / Setters privados
Este es el último bastión de getters y setters que trabajan directamente en el tipo. De hecho, ni siquiera llamaría a estos captadores y establecedores sino a accesores y manipuladores.
En este contexto, a veces manipular una porción específica de la estructura de datos siempre / casi siempre / generalmente requiere que se lleve una contabilidad específica. Supongamos que cuando actualiza la raíz de un árbol, la memoria caché de lado necesita ser purgada, o cuando accede al elemento de datos externo, se necesita obtener / liberar un bloqueo. En estos casos, tiene sentido aplicar el principal DRY y agrupar esas acciones juntas.
Dentro del contexto privado, todavía es posible que las otras funciones de la familia eluden a estos 'captadores y establecedores' y manipulen la estructura de datos. Por eso pienso en ellos más como accesores y manipuladores. Puede acceder a los datos directamente o confiar en otro miembro de la familia para que esa parte sea correcta.
Getters / Setters Protegidos
En un contexto protegido, no es terriblemente diferente a un contexto público. Las funciones extrañas posiblemente deshonestas desean acceder a la estructura de datos. Entonces no, si existen operan como captadores / setters públicos.
this->variable = x + 5
, o llamar a unaUpdateStatistics
función en setter, y en esos casosclassinstancea->variable = 5
causará problemas.