Si una variable tiene getter y setter, ¿debería ser pública?

23

Tengo una clase con una variable que es privada y la clase tiene un getter y un setter para esa variable. ¿Por qué no hacer pública esa variable?

El único caso que creo que tiene que usar getters y setters es si necesita hacer alguna operación además del set o get. Ejemplo:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}
Oni
fuente
10
Los captadores y los colocadores están ahí para protegerse. Algún día puede querer hacer this->variable = x + 5, o llamar a una UpdateStatisticsfunción en setter, y en esos casos classinstancea->variable = 5causará problemas.
Codificador
1
Otro ejemplo es: set_inch, set_centimeter, get_inch, get_centimeter, con algunas acciones espeluznantes.
rwong
Sí, getters y setters lo ayudan a controlar sus variables de instancia de la forma en que desea que se controlen.
developerdoug
77
@Coder: ¿siempre puedes agregar tu getter / setter cuando sea necesario? ¿O es algo que no puedes hacer fácilmente en C ++ (mi experiencia es principalmente de Delphi)?
Marjan Venema
Esta pregunta: programmers.stackexchange.com/questions/21802/… puede ser de interés.
Winston Ewert

Respuestas:

21

¿Alguna vez has oído hablar de una propiedad?

Una propiedad es un campo que tiene accesores "incorporados" (captadores y establecedores). Java, por ejemplo, no tiene propiedades, pero se recomienda escribir los captadores y definidores en un campo privado. C # tiene propiedades.

Entonces, ¿por qué necesitamos getters y setters? Básicamente lo necesitamos para proteger / proteger el campo. Por ejemplo, no está accediendo al campo en la referencia de memoria, está accediendo a un método que luego cambiará el campo (referencia). Ese método puede realizar algunas operaciones que un usuario no está dispuesto a conocer ( comportamiento de encapsulación ), como en su ejemplo. Imagine, por ejemplo, que una docena de clases usan su campo público y necesita cambiar la forma en que se usa ... Tendría que mirar cada una de esas clases para cambiar la forma en que usan el campo ... No entonces "OOlysh".

Pero, por ejemplo, si tiene un campo booleano llamado muerto. Debería pensarlo dos veces antes de declarar un setDead e isDead. Debería escribir accesores que sean legibles por humanos , por ejemplo, kill () en lugar de setDead.

Sin embargo, hay muchos marcos que suponen que está siguiendo la convención de nomenclatura JavaBean (hablando de Java aquí), por lo tanto, en esos casos, debe declarar todos los captadores y establecedores después de la convención de nomenclatura.

wleao
fuente
2
Ahora "legible por humanos" es finalmente un argumento que puedo "asimilar". Todos los demás argumentos que normalmente se oye son una especie de prueba de futuro que solo se tratan con la misma facilidad cuando sea necesario ...
Marjan Venema
13
Su desprecio por las "pruebas futuras" es común, pero está equivocado. Sí, puede abordar dichos cambios cuando surja la necesidad, si es el único cliente de ese código . Si nunca publica su solución para clientes externos, simplemente puede incurrir en una deuda técnica y nadie necesita saberlo. Pero los proyectos internos tienen la costumbre de hacerse públicos cuando menos lo esperas, y entonces ¡ay de ti!
Kilian Foth
2
En realidad planteas un punto fantástico. kill no es un set / get, es una acción. Oculta completamente la implementación: así es como codifica OO. Las propiedades, por otro lado, son tan estúpidas como los setters / getters, más estúpidas incluso porque la sintaxis fácil alienta a las personas a agregarlas a las variables cuando ni siquiera son necesarias (OO-Furbar esperando que suceda). ¿Quién pensaría usar kill () cuando simplemente podrían agregar una etiqueta de "propiedad" a su bandera muerta?
Bill K
1
La pregunta está etiquetada como una pregunta de C ++. C ++ no admite propiedades, entonces, ¿por qué publicar esto?
Thomas Eding
1
@ThomasEding: C ++ es algo único en tener operadores de asignación reemplazables. Si bien C ++ no utiliza el término "propiedad" de la misma manera que los lenguajes como C # o VB.NET, el concepto sigue ahí. Es posible en C ++ usar la sobrecarga del operador para que foo.bar = biz.baz+5se llame a una función bizpara evaluar baz, luego se agreguen cinco y se llame a una función foopara establecer barel valor resultante. Dichas sobrecargas no se denominan "propiedades", pero pueden servir para el mismo propósito.
supercat
25

Esta no es la opinión más popular, pero no veo mucha diferencia.

Los setters y getters son una idea bastante mala. Lo he pensado y, sinceramente, no puedo encontrar una diferencia entre un setter / getter, una propiedad y una variable pública en la práctica.

En THEORY, un setter y getter o property agregan un lugar para tomar algunas acciones adicionales cuando se establece / obtiene una variable y, en teoría, aíslan su código de los cambios.

En realidad, rara vez veo setters y getters utilizados para agregar una acción, y cuando quieres agregar una acción, quieres agregarla a TODOS los setters o getters de una clase (como el registro), lo que debería hacerte pensar que debería Ser una mejor solución.

En cuanto a aislar las decisiones de diseño, si cambia un int a un largo todavía tiene que cambiar sus setters y al menos verificar cada línea que accede a ellas a mano, sin mucho aislamiento allí.

Las clases mutables deben evitarse de forma predeterminada de todos modos, por lo que agregar un setter debería ser el último recurso. Esto se mitiga con el patrón de construcción donde se puede establecer un valor hasta que el objeto esté en el estado deseado, entonces la clase puede volverse inmutable y sus establecedores generarán excepciones.

En cuanto a los captadores, todavía no puedo encontrar mucha diferencia entre un captador y una variable final pública. El problema aquí es que es malo OO en cualquier caso. No debería pedir un valor de un objeto y operarlo, debería pedirle a un objeto que haga una operación por usted.

Por cierto, no estoy abogando de ninguna manera por las variables públicas: digo que los establecedores y captadores (e incluso las propiedades) están demasiado cerca de ser variables públicas.

El gran problema es simplemente que las personas que no son programadores OO están demasiado tentados a usar setters y getters para convertir los objetos en bolas de propiedad (estructuras) que se transmiten y operan, casi todo lo contrario de cómo funciona el código orientado a objetos.

Bill K
fuente
Una cosa buena, aparte de otras respuestas aquí, sobre getter setter es: puedo agregar algunas validaciones / conversiones dentro de ella antes de que la variable privada real asuma su valor.
WinW
1
@Kiran es cierto, pero si lo configura en el constructor puede tener validaciones Y tiene una clase inmutable. Para los setters no estoy diciendo que una variable pública de escritura sea buena, estoy diciendo que incluso los setters son malos. Para los captadores, podría considerar una variable final pública como una alternativa viable.
Bill K
En algunos lenguajes como C #, las propiedades no son compatibles con las variables públicas binarias, por lo que si hace que una variable pública forme parte de su API, pero luego desea agregar alguna validación, romperá el programa de su cliente cuando lo convierta a una propiedad. Es mejor que sea una propiedad pública desde el principio.
Robert Harvey
@RobertHarvey Eso todavía significa que estás exponiendo propiedades. Todo mi punto era que las propiedades de exposición deben evitarse, y cuando sea necesario, debes tratar de restringirlas a los captadores y hacer que tu clase sea inmutable. Si su clase es inmutable, ¿por qué necesitaría código en un getter? Y, por lo tanto, se deduce que es posible que no tenga un getter. Si no puede escribir una clase sin propiedades mutables expuestas, entonces sí, un setter siempre es mejor que una variable pública grabable (y recibir un disparo en el pie siempre es mejor que ser apuñalado en el intestino).
Bill K
1
El estado mutable es aceptable, pero nuevamente debe pedirle a su objeto que haga algo por usted, no que le haga algo a su objeto; por lo tanto, cuando muta su estado, un setter no es una excelente manera de hacerlo, intente escribir un método comercial con lógica en su lugar Por ejemplo, "move (Up5) en lugar de setZLocation (getZLocation () + 5)".
Bill K
8

El usuario de getters y setters entra en el principio de encapsulación . Esto te permitirá cambiar cómo funcionan las cosas dentro de la clase y mantener todo funcionando.

Por ejemplo, si otros 3 objetos llaman foo.barpara obtener el valor de la barra y usted decide cambiar el nombre de la barra demasiado lejos, tiene un problema en sus manos. Si los objetos llamados foo.bartendrían que cambiar todas las clases que tienen esto. Si se usa un setter / getter, entonces no tiene nada que cambiar. Otra posibilidad es cambiar el tipo de la variable, en cuyo caso solo agregue un código de transformación al captador / definidor y estará bien.


fuente
1
+1: la variable miembro es la implementación, el captador y el definidor son la interfaz. Solo la interfaz debe ser pública. Además, los nombres getter y setter deben tener sentido en términos de una abstracción, independientemente de si hay una variable miembro simple subyacente o alguna otra implementación. No son excepciones - los casos en que un subsistema construido a partir de clases acopladas herméticamente es la forma más fácil - pero eso sólo empuja el límite de datos-escondite hasta un nivel de todos modos, a la clase que representa a todo el subsistema.
Steve314
¿No podría simplemente presentar el getter y / o setter cuando necesito cambiar el trabajo dentro de la clase? ¿Es algo que se hace fácilmente en Delphi, pero tal vez no en otros idiomas?
Marjan Venema
@ marjan Claro, puede presentar el getter / setter en cualquier momento más adelante. El problema es que debe regresar y cambiar TODO el código que estaba usando el campo público en lugar de getter / setter. No estoy seguro de si soy yo quien está confundido o tú lo eres. 0.o
Pete
3
Personalmente, creo que este es un argumento un poco falso. Si cambia el significado de una variable pero la variable tiene un captador y definidor, entonces no importa cómo se llame. No es una parte visible de la interfaz. Es mucho más probable que desee cambiar el nombre de los captadores y definidores para indicar a los clientes el cambio o la aclaración del significado de la variable encapsulada. En este último caso, no está en una mejor posición que con una variable pública. Esto es bastante aparte del argumento de que una variable con getters y setters está más expuesta que encapsulada.
CB Bailey
1
Una operación de refactorización es prácticamente gratuita de cualquier manera, por lo que realmente no compro esta. Además, es una muy mala forma usar un setter o getter a menos que lo necesites absolutamente; eventualmente obtendrás personas que no entienden el concepto simplemente agregando setters y getters para los miembros en lugar de cuando son necesarios, que es solo gotear semillas de maldad en toda su base de código.
Bill K
5

El uso de captadores y establecedores también le permite controlar qué contenido se almacena en una variable particular. Si el contenido debe ser de cierto tipo o valor, parte del código de establecimiento puede ser garantizar que el nuevo valor cumpla con estos requisitos. Si la variable es pública, no puede garantizar que se cumplan estos requisitos.

Este enfoque también hace que su código sea más adaptable y manejable. Es mucho más fácil hacer cambios en la arquitectura de una clase si tiene funciones que mantienen esa arquitectura oculta de todas las otras clases o funciones que utilizan esa clase. El cambio ya mencionado de un nombre de variable es solo uno de los muchos cambios que son mucho más fáciles de realizar si tiene funciones como getters y setters. La idea general es mantener la mayor privacidad posible, especialmente las variables de su clase.

Kenneth
fuente
¡Muchas gracias! Estaba pensando en el mismo caso que mencionas. Si quiero que una variable tenga contenido en [0,1], debería verificar el valor entrante en su setter :)
Oni
Cuando usted es el único desarrollador que trabaja en un proyecto, es fácil pensar que cosas como esta son inútiles, pero cuando se encuentre trabajando con un equipo, descubrirá que el hábito de técnicas como esta será muy útil y lo ayudará. Evite muchos errores en el camino.
Kenneth
Si está usando C #, use propiedades, puede usar una propiedad para establecer una instancia privada si es necesario en función de alguna lógica en la parte de establecimiento de la propiedad.
developerdoug
2

Usted dice "El único caso que creo que tiene que usar getters y setters es si necesita hacer alguna operación además del set o el get".

Debe utilizar captadores y definidores si en algún momento en el futuro que puede ser que tenga que hacer alguna operación además del conjunto y obtener y no querer cambiar miles de líneas de código fuente cuando eso sucede.

Debe usar getters y setters si no desea que alguien tome la dirección de la variable y la pase, con consecuencias desastrosas si esa variable se puede cambiar sin ningún código fuente que lo mencione, o incluso después de que el objeto deje de existir. .

gnasher729
fuente
Su último párrafo hace un excelente punto que corta en ambos sentidos: requerir el uso de captadores y establecedores evitará que otro código tome la dirección de algo. En los casos en que habría beneficios significativos y pocas desventajas de tener un código externo que tome las direcciones de las cosas, dicha restricción podría ser algo malo. Si hubiera pocos beneficios y desventajas severas para permitir tal comportamiento, restringirlo podría ser algo bueno.
supercat
1

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.

Kain0_0
fuente
0

Desde la experiencia C ++, el setter / getter es útil para 2 escenarios:

  1. si tiene que realizar una comprobación de cordura antes de asignar valor al miembro como cadenas o matriz.
  2. puede ser útil cuando se necesita la sobrecarga de funciones, como el valor int para la asignación de bool cuando -1 (o valores negativos) es falso. y viceversa para getter.

Aparte de eso, la seguridad es un punto válido, pero su importancia se limita a muy pocas aplicaciones de desarrollo, como los módulos relacionados con el inicio de sesión o el acceso db. ¿Por qué molestarse en codificar más cuando solo unas pocas personas usan su módulo?

Ajay nandoriya
fuente