Me metí en un interesante argumento de Internet sobre los métodos get y setter y la encapsulación. Alguien dijo que todo lo que deberían hacer es una asignación (establecedores) o un acceso variable (captadores) para mantenerlos "puros" y garantizar la encapsulación.
- ¿Estoy en lo cierto de que esto derrotaría por completo el propósito de tener captadores y establecedores en primer lugar y debería permitirse la validación y otra lógica (sin efectos secundarios extraños, por supuesto)?
- ¿Cuándo debe ocurrir la validación?
- Al establecer el valor, dentro del setter (para proteger el objeto de entrar en un estado no válido, mi opinión)
- Antes de establecer el valor, fuera del setter
- Dentro del objeto, antes de cada vez que se usa el valor
- ¿Se permite a un setter cambiar el valor (tal vez convertir un valor válido en alguna representación interna canónica)?
object-oriented
language-agnostic
encapsulation
Botá Balázs
fuente
fuente
Respuestas:
Recuerdo haber tenido una discusión similar con mi profesor cuando aprendí C ++ en la universidad. Simplemente no podía ver el punto de usar getters y setters cuando podía hacer pública una variable. Ahora entiendo mejor con años de experiencia y he aprendido una razón mejor que simplemente decir "mantener la encapsulación".
Al definir los captadores y establecedores, proporcionará una interfaz coherente para que si desea cambiar su implementación, sea menos probable que rompa el código dependiente. Esto es especialmente importante cuando sus clases están expuestas a través de una API y se utilizan en otras aplicaciones o por terceros. Entonces, ¿qué pasa con las cosas que entran en el getter o setter?
Los captadores generalmente están mejor implementados como un simple paso hacia abajo para acceder a un valor porque esto hace que su comportamiento sea predecible. Digo en general, porque he visto casos en los que los captadores se han utilizado para acceder a valores manipulados por cálculo o incluso por código condicional. En general, no es tan bueno si está creando componentes visuales para usar en tiempo de diseño, pero aparentemente es útil en tiempo de ejecución. Sin embargo, no existe una diferencia real entre esto y el uso de un método simple, excepto que cuando usa un método, generalmente es más probable que nombre un método de manera más apropiada para que la funcionalidad del "getter" sea más evidente al leer el código.
Compare lo siguiente:
y
La segunda opción deja en claro que el valor se está calculando, mientras que el primer ejemplo le dice que simplemente está devolviendo un valor sin saber nada sobre el valor en sí.
Quizás podría argumentar que lo siguiente sería más claro:
Sin embargo, el problema es que está asumiendo que el valor ya ha sido manipulado en otro lugar. Por lo tanto, en el caso de un captador, si bien es posible que desee suponer que algo más podría estar sucediendo cuando devuelve un valor, es difícil aclarar esas cosas en el contexto de una propiedad, y los nombres de las propiedades nunca deben contener verbos de lo contrario, es difícil entender de un vistazo si el nombre utilizado debe estar decorado con paréntesis cuando se accede.
Sin embargo, los setters son un caso ligeramente diferente. Es completamente apropiado que un establecedor proporcione un procesamiento adicional para validar los datos que se envían a una propiedad, lanzando una excepción si establecer un valor violaría los límites definidos de la propiedad. Sin embargo, el problema que tienen algunos desarrolladores al agregar procesamiento a los establecedores es que siempre existe la tentación de que el colocador haga un poco más, como realizar un cálculo o una manipulación de los datos de alguna manera. Aquí es donde puede obtener efectos secundarios que en algunos casos pueden ser impredecibles o indeseables.
En el caso de los setters, siempre aplico una regla general simple, que es hacer lo menos posible a los datos. Por ejemplo, generalmente permitiré la prueba de límites y el redondeo para que pueda plantear excepciones si es apropiado, o evitar excepciones innecesarias donde puedan evitarse de manera sensata. Las propiedades de punto flotante son un buen ejemplo en el que es posible que desee redondear los decimales excesivos para evitar generar una excepción, al tiempo que permite ingresar valores de rango con algunos decimales adicionales.
Si aplica algún tipo de manipulación de la entrada del setter, tiene el mismo problema que con el getter, que es difícil permitir que otros sepan lo que está haciendo el setter simplemente nombrándolo. Por ejemplo:
¿Le dice esto algo sobre lo que va a pasar con el valor cuando se le da al setter?
Qué tal si:
El segundo ejemplo le dice exactamente qué va a pasar con sus datos, mientras que el primero no le permitirá saber si su valor se modificará arbitrariamente. Al leer el código, el segundo ejemplo será mucho más claro en su propósito y función.
Tener getters y setters no se trata de encapsular en aras de la "pureza", sino de encapsular para permitir que el código se refactorice fácilmente sin arriesgarse a un cambio en la interfaz de la clase que de lo contrario rompería la compatibilidad de la clase con el código de llamada. La validación es totalmente apropiada en un setter, sin embargo, existe un pequeño riesgo de que un cambio en la validación pueda romper la compatibilidad con el código de llamada si el código de llamada se basa en la validación que ocurre de una manera particular. Esta es una situación generalmente rara y de riesgo relativamente bajo, pero debe tenerse en cuenta en aras de la integridad.
La validación debe realizarse dentro del contexto del establecedor antes de establecer realmente el valor. Esto garantiza que si se produce una excepción, el estado de su objeto no cambiará y potencialmente invalidará sus datos. En general, me parece mejor delegar la validación a un método separado que sería lo primero que se llama dentro del configurador, para mantener el código del configurador relativamente ordenado.
En casos muy raros, tal vez. En general, probablemente sea mejor no hacerlo. Este es el tipo de cosas que mejor se deja a otro método.
fuente
Si el getter / setter simplemente refleja el valor, entonces no tiene sentido tenerlos o hacer que el valor sea privado. No tiene nada de malo hacer públicas algunas variables miembro si tiene una buena razón. Si está escribiendo una clase de puntos 3d, entonces tener público .x, .y, .z tiene mucho sentido.
Como dijo Ralph Waldo Emerson: "Una consistencia tonta es el duende de las mentes pequeñas, adoradas por los pequeños estadistas y filósofos y los diseñadores de Java".
Los captadores / establecedores son útiles donde puede haber efectos secundarios, donde necesita actualizar otras variables internas, recalcular valores almacenados en caché y proteger la clase de entradas no válidas.
La justificación habitual para ellos, que ocultan la estructura interna, es generalmente la menos útil. p.ej. Tengo estos puntos almacenados como 3 flotantes, podría decidir almacenarlos como cadenas en una base de datos remota, así que haré getters / setters para ocultarlos, como si pudieras hacer eso sin tener ningún otro efecto en el código de la persona que llama.
fuente
IEnumerable<T>
que forzarla a algo asíList<T>
. Diría que su ejemplo de acceso a la base de datos está violando una responsabilidad única: mezclar la representación del modelo con la forma en que persiste.Principio de acceso uniforme de Meyer: "Todos los servicios ofrecidos por un módulo deben estar disponibles a través de una notación uniforme, que no traicione si se implementan a través del almacenamiento o la computación". es la razón principal detrás de los captadores / establecedores, también conocidos como Propiedades.
Si decide almacenar en caché o calcular con pereza un campo de una clase, puede cambiar esto en cualquier momento si solo ha expuesto los accesores de propiedad y no los datos concretos.
Los objetos de valor, las estructuras simples no necesitan esta abstracción, pero una clase completa, en mi opinión.
fuente
Una estrategia común para diseñar clases, que se introdujo a través del lenguaje Eiffel, es la Separación de consulta de comandos . La idea es que un método debe o bien que decir algo sobre el objeto o indicar al objeto de hacer algo, pero no hacer las dos cosas.
Esto solo se relaciona con la interfaz pública de la clase, no con la representación interna. Considere un objeto de modelo de datos respaldado por una fila en la base de datos. Puede crear el objeto sin cargar los datos, luego, la primera vez que llama a un captador, realmente lo hace
SELECT
. Está bien, es posible que esté cambiando algunos detalles internos sobre cómo se representa el objeto, pero no está cambiando la forma en que aparece para los clientes de ese objeto. Debería poder llamar a los captadores varias veces y seguir obteniendo los mismos resultados, incluso si realizan un trabajo diferente para devolver esos resultados.De manera similar, un setter se ve, contractualmente, como si solo cambiara el estado de un objeto. Podría hacerlo de una manera enrevesada: escribir un archivo
UPDATE
en una base de datos o pasar el parámetro a algún objeto interno. Eso está bien, pero sería sorprendente hacer algo que no esté relacionado con la configuración del estado.Meyer (el creador de Eiffel) también tenía cosas que decir sobre la validación. Esencialmente, cada vez que un objeto está inactivo debe estar en un estado válido. Entonces, justo después de que el constructor haya terminado, antes y después (pero no necesariamente durante) cada llamada a un método externo, el estado del objeto debe ser consistente.
Es interesante notar que en ese lenguaje, la sintaxis para llamar a un procedimiento y para leer un atributo expuesto se ve igual. En otras palabras, la persona que llama no puede saber si está usando algún método o trabajando directamente con una variable de instancia. Es solo en idiomas que no ocultan este detalle de implementación donde surge la pregunta: si las personas que llaman no pueden decirlo, puede cambiar entre un ivar público y accesores sin filtrar ese cambio en el código del cliente.
fuente
Otra cosa aceptable para hacer es la clonación. En algunos casos, debe asegurarse de que después de que alguien le dé su clase, por ejemplo. una lista de algo, él no puede cambiarlo dentro de su clase (ni cambiar el objeto en esa lista). Por lo tanto, realiza una copia profunda del parámetro en setter y devuelve una copia profunda en getter. (El uso de tipos inmutables como parámetros es otra opción, pero lo anterior supone que no es posible) Pero no clone en accesores si no es necesario. Es fácil pensar (pero no de manera adecuada) acerca de las propiedades / captadores y establecedores como operaciones de costo constante, por lo que este es el rendimiento de las minas terrestres esperando al usuario de la API.
fuente
No siempre hay un mapeo 1-1 entre los accesos de propiedad y los ivars que almacenan los datos.
Por ejemplo, una clase de vista puede proporcionar una
center
propiedad aunque no haya ivar que almacene el centro de la vista; la configuracióncenter
provoca cambios en otros ivars, comoorigin
otransform
lo que sea, pero los clientes de la clase no saben ni les importa cómocenter
se almacenan, siempre que funcionen correctamente. Sin embargo, lo que no debería suceder es que la configuracióncenter
hace que las cosas sucedan más allá de lo necesario para guardar el nuevo valor, sin embargo, eso se hace.fuente
La mejor parte de los setters y getters es que facilitan alterar las reglas de una API sin cambiar la API. Si detecta un error, es mucho más probable que pueda corregir el error en la biblioteca y no hacer que cada consumidor actualice su base de código.
fuente
Tiendo a creer que los setters y getters son malvados y solo deberían usarse en clases administradas por un framework / contenedor. Un diseño de clase adecuado no debería producir captadores y colocadores.
Editar: un artículo bien escrito sobre este tema .
Edit2: los campos públicos son una tontería en un enfoque OOP; Al decir que los captadores y los colocadores son malvados, no quiero decir que deberían ser reemplazados por campos públicos.
fuente