2010-04-01 16 views
10

Recientemente leí que los getters/setters son malvados y tengo que decir que tiene sentido, pero cuando comencé a aprender OOP una de las primeras cosas que aprendí fue "Encapsular tus campos", así que aprendí a crear clases, darle algunos campos, crear getters, setters para ellos y crear constructor donde inicializo estos campos. Y cada vez que otra clase necesita manipular este objeto (o, por ejemplo, mostrarlo), le paso el objeto y lo manipula usando getters/setters. Puedo ver problemas con este enfoque.¿Corregir el diseño OOP sin getters?

Pero cómo hacerlo bien? Por ejemplo, mostrar/renderizar un objeto que es una clase de "datos", digamos Persona, que tiene nombre y fecha de nacimiento. ¿Debería la clase tener un método para mostrar el objeto donde algún Renderer se pasaría como argumento? ¿No violaría esto el principio de que la clase debería tener un solo propósito (en este caso, almacenar estado), por lo que no debería importarle la presentación de este objeto.

¿Puede sugerir algunos buenos recursos donde se presentan las mejores prácticas en el diseño de OOP? Estoy planeando comenzar un proyecto en mi tiempo libre y quiero que sea mi proyecto de aprendizaje en el diseño de OOP correcto.

+8

es necesario encontrar una mejor fuente de material de lectura, mi amigo. No todo lo que lees en los intertubos es cierto. Getters/Setters son lo opuesto al mal. Si hubieras publicado la fuente de esta joya de la sabiduría, podríamos separarla, pero, por desgracia, no hay ningún enlace ... aha-- enlace publicado. ya criticado noop. –

+4

@Sky Todo depende del contexto, algunos usos de Getters/Setters son buenos y otros son "malvados" (aunque no habría usado esa palabra). Intento no ser tan dogmático. –

+0

@Andreas - entendido. El uso indebido de cualquier construcción puede ser malo, pero caracterizar a los buscadores/establecedores como malvados en el mismo contexto que, por ejemplo, eval es simplemente tonto, IMO. –

Respuesta

11

Allen Holub hizo un gran chapoteo con "Why getter and setter methods are evil" en 2003.

Es muy bueno que has encontrado y leído el artículo. Admiro a cualquiera que esté aprendiendo y pensando críticamente sobre lo que están haciendo.

Pero tome al Sr. Holub con un grano de sal.

Esta es una vista que llamó mucho la atención por su posición extrema y el uso de la palabra "mal", pero no ha prendido fuego al mundo ni ha sido generalmente aceptado como dogma.

Mire C#: de hecho agregaron azúcar sintáctico al lenguaje para hacer que las operaciones get/set sean más fáciles de escribir. O esto confirma la opinión de alguien sobre Microsoft como un imperio malvado o contradice la declaración del Sr. Holub.

El hecho es que las personas escriben objetos para que los clientes puedan manipular el estado. No significa que cada objeto escrito de esa manera es incorrecto, malo o impracticable.

La vista extrema no es práctica.

+0

En realidad, el artículo del señor Holub tiene toda la razón. El título suena extremo. Es triste, que la gente deje de leer después de él:/ – MaR

+0

@MaR Totalmente de acuerdo, se opone innecesariamente a muchas personas que de otra manera estarían dispuestas a escuchar lo que tienes que decir. –

+0

Leí el artículo. Allen Holub es un tipo muy inteligente, pero el hecho es que nadie está siguiendo su consejo. Los objetos todavía se están escribiendo, el código se está ejecutando, y cualquier problema que pueda surgir no se puede colocar a los pies de getters y setters. Es un argumento académico en este punto. – duffymo

1

¿Por qué crees que los consumidores son malos? Ver un post con respuestas que demuestre lo contrario:

Purpose of private members in a class

mi humilde opinión que contiene una gran cantidad de lo que por derecho se puede llamar "oop mejores prácticas".

Actualización: Bien, leyendo el artículo al que se refiere, entiendo más claramente de qué se trata el problema. Y es una historia completamente diferente de lo que sugiere el título provocativo del artículo. Todavía no he leído el artículo completo, pero AFAIU el punto básico es que uno no debería publicar innecesariamente campos de clase a través de getters y setters agregados sin pensar (o generados). Y con este punto, estoy totalmente de acuerdo.

Al diseñar cuidadosamente y se centra en lo que debe hacer en lugar de cómo lo vas a hacer, se elimina la gran mayoría de los métodos de captador/definidor en su programa. No solicite la información que necesita para realizar el trabajo; pregunte al objeto que tiene la información para que haga el trabajo por usted.

Hasta ahora todo bien. Sin embargo, no estoy de acuerdo que la provisión de un captador como esto

int getSomeField(); 

inherentemente compromete su diseño clase. Bueno, lo hace, si no ha diseñado bien su interfaz de clase. Entonces, por supuesto, podría ocurrir que se dé cuenta demasiado tarde de que el campo debería ser long en lugar de int, y cambiarlo rompería 1000 lugares en el código del cliente. En mi humilde opinión, en ese caso, el diseñador es el culpable, no el pobre comprador.

+0

No dijo que cree que son malvados, pero que leyó que sí lo son. – lugte098

+0

No me refiero a que deberías estar usando campos públicos, por supuesto, lo que yo hago y que no deberías estar exponiendo ningún detalle sobre la implementación de clases, en este caso qué campos tiene a menos que sea absolutamente necesario. El artículo está aquí http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html – kane77

+0

@ lugte098 "Recientemente leí que los getters/setters son malvados y tengo que decir que tiene sentido " –

4

"encapsular sus campos", así que aprendieron a crear la clase darle algunos campos, crear captadores, organismos

gente Python no hacen esto. Sin embargo, todavía están haciendo programación OO. Claramente, los getters y setters quisquillosos no son esenciales.

Son comunes, debido a las limitaciones en C++ y Java. Pero ellos no parecen ser esenciales.

Python personas usan properties a veces para crear un getter y setter funciones que parecen un atributo simple.

El punto es que "Encapsulación" es una estrategia Diseño. Tiene poco o nada que ver con la implementación. Puede tener todos los atributos públicos, y aún así un diseño muy bien encapsulado.

También tenga en cuenta que muchas personas se preocupan por "otra persona" que "viola" el diseño al acceder directamente a los atributos. Supongo que esto podría suceder, pero luego la clase dejará de funcionar correctamente.

En C++ (y Java) donde no puede ver la fuente, puede ser difícil de entender la interfaz, por lo que necesita muchas sugerencias. métodos privados, captadores y definidores explícitos, etc.

En Python, donde se puede ver toda la fuente, es trivial comprender la interfaz. No necesitamos proporcionar tantos consejos. Como decimos "Use the source, Luke" y "We're all adults here". Todos somos capaces de ver la fuente, no necesitamos ser quisquillosos con la acumulación de getters y setters para proporcionar más pistas sobre cómo funciona la API.

Por ejemplo, mostrar/representar el objeto que es clase de "datos" - digamos Persona, que tiene nombre y fecha de nacimiento. ¿Debería la clase tener un método para mostrar el objeto donde algún Renderer se pasaría como argumento?

Buena idea.

¿No violaría eso que la clase solo debería tener un propósito (en este caso, almacenar estado) por lo que no debería importarle la presentación de este objeto.

Es por eso que el objeto Render está separado. Tu diseño es bastante agradable.

No hay motivo por el cual un objeto Persona no puede llamar a un procesador de propósito general y aún tiene un conjunto limitado de responsabilidades.Después de todo, el objeto Person es responsable de los atributos, y pasar esos atributos a un Renderer está dentro de sus responsabilidades.

Si es realmente un problema (y puede serlo en algunas aplicaciones), puede introducir las clases Helper. Entonces, la clase PersonRenderer hace la representación de datos Person. De esta forma, un cambio en Person también requiere cambios en PersonRenderer, y nada más. Este es el patrón de diseño Objeto de acceso a datos.

Algunas personas harán que la Render sea una clase interna, incluida en Person, por lo que es Person.PersonRenderer para imponer una contención más seria.

+0

Con Python vs Java, también es un problema de sintaxis: con Python, la sintaxis para acceder a un campo es la misma que la de un getter. Esto significa que puede cambiar la implementación sin afectar todo el código que usa la clase. Con Java, la diferencia de implementación está expuesta a los consumidores de la clase, ya sea instancia.foo o instancia.getFoo(), por lo que un cambio en la implementación se propaga a través de las dependencias. Con Python (y Ruby y C#) siempre es instancia.foo, por lo que no se requiere una regla dogmática "siempre encapsula tus campos". – Douglas

+0

¡Gracias por mencionar el patrón "Objeto de acceso a datos"! –

0

Todo lo que es público es parte de la API de la clase. Cambiar estas partes puede romper otras cosas, confiando en eso. Un campo público, que no solo esté conectado con una API, sino con una representación interna, puede ser riesgoso. Ejemplo: Guarda los datos en un campo como una matriz. Esta matriz es pública, por lo que los datos pueden cambiarse de otras clases. Más tarde decides cambiar a una lista genérica. El código que usa este campo como una matriz está roto.

+0

Creo que eso es a lo que intenta llegar la discusión. ¿Lo dejas en público y lo abres a tal abuso, o creas sin pensarlo a los getters y setters y luego lo abres a ese abuso? – Anurag

4

Si tiene getters y setters, no tiene encapsulamiento. Y no son necesarios. Considere la clase std :: string. Esto tiene una representación interna bastante complicada, pero no tiene getters o setters, y solo un elemento de la representación es (probablemente) expuesto simplemente devolviendo su valor (es decir, size()). Ese es el tipo de cosa que debería estar buscando.

+2

Estoy de acuerdo contigo aunque no creo que 'std :: string' sea el mejor ejemplo. El operador '' [] 'en la cadena en realidad es una forma de funciones get/set. –

1

Supongamos que tiene muchas clases de entidad en sus diseños, y suponga que tienen una clase base como Datos. Agregar diferentes métodos getter y setter para implementaciones concretas contaminará el código del cliente que usa estas entidades, como muchos dynamic_casts, para llamar a los métodos requeridos getter y setter.

Por lo tanto, los métodos getter y setter puede permanecer donde están, pero el código de cliente que debe proteger. Mi recomendación sería aplicar Patrón de visitante o recopilador de datos para estos casos.

En otras palabras, pregúntese por qué necesito estos métodos de acceso, ¿cómo puedo manipular estas entidades? Y luego aplique estas manipulaciones en las clases de Visitante para mantener limpio el código del cliente, y también extienda la funcionalidad de las clases de entidad sin contaminar su código.

1

En algunos idiomas, como C++, está el concepto de friend. Al utilizar este concepto, puede hacer que los detalles de implementación de una clase sean visibles solo para un subconjunto de otras clases (o incluso funciones). Cuando utilizas Get/Set indiscriminadamente, le das a todos acceso a todo.

Cuando se utiliza con moderación friend es una forma excelente de aumentar la encapsulación.

2

El concepto básico de por qué se considera que es el mal, que una clase/objeto debe exportar función y no estatal. El estado de un objeto está hecho de sus miembros. Getters y Setters permiten a los usuarios externos leer/modificar el estado de un objeto sin utilizar ninguna función.

De ahí la idea de que, a excepción de DataTransferObjects para los que podría tener Getters y un constructor para establecer el estado, los miembros de un objeto solo deberían modificarse llamando a una funcionalidad de un objeto.

1

En el siguiente documento relativo a endotesting encontrará un patrón para evitar captadores (en algunos casos) con lo que los manipuladores 'inteligentes' que el autor denomina. Tiene mucho en común con la forma en que Holub se acerca a evitar algunos captadores.

http://www.mockobjects.com/files/endotesting.pdf