No estoy buscando una opinión sobre la semántica, sino simplemente un caso en el que tener captadores utilizados de manera sensata es un impedimento real. Tal vez me arroja a una espiral interminable de confiar en ellos, tal vez la alternativa es más limpia y maneja los captadores automáticamente, etc. Algo concreto.
Escuché todos los argumentos, escuché que son malos porque te obligan a tratar los objetos como fuentes de datos, que violan el "estado puro" de un objeto de "no dar demasiado, pero prepárate para aceptar mucho ".
Pero no hay ninguna razón sensata para explicar por qué getData
es malo, de hecho, algunas personas argumentaron que se trata mucho de la semántica, los captadores como buenos por sí mismos getX
, pero para mí no los nombran , esto es al menos divertido .
¿Qué es una cosa, sin opiniones, que se romperá si uso captadores con sensatez y para datos que claramente la integridad del objeto no se rompe si lo saca?
Por supuesto, permitir un captador para una cadena que se usa para cifrar algo es más que tonto, pero estoy hablando de datos que su sistema necesita para funcionar. Tal vez sus datos se extraen de un Provider
objeto, pero, aún así, el objeto aún necesita permitir Provider
que se haga $provider[$object]->getData
, no hay forma de evitarlo.
Por qué pregunto: Para mí, los captadores, cuando se usan con sensatez y en datos que se tratan como "seguros" son enviados por Dios, el 99% de mis captadores se usan para identificar el objeto, como, en el código Object, what is your name? Object, what is your identifier?
, pregunto . cualquiera que trabaje con un objeto debería saber estas cosas acerca de un objeto, porque casi todo lo relacionado con la programación es identidad y, ¿quién más sabe mejor qué es el objeto en sí? Así que no veo ningún problema real a menos que seas un purista.
He examinado todas las preguntas de StackOverflow sobre "por qué los captadores / setters" son malos y, aunque estoy de acuerdo en que los setters son realmente malos en el 99% de los casos, los getters no tienen que ser tratados de la misma manera solo porque riman.
Un setter comprometerá la identidad de su objeto y hará que sea muy difícil depurar quién está cambiando los datos, pero un getter no está haciendo nada.
fuente
Respuestas:
No se puede escribir un buen código sin captadores.
La razón por la cual no es porque los captadores no rompen la encapsulación, lo hacen. No es porque los captadores no tienten a las personas a no molestarse en seguir OOP, lo que les haría poner métodos con los datos sobre los que actúan. Ellas hacen. No, necesitas captadores debido a los límites.
Las ideas de encapsulación y métodos de mantenimiento junto con los datos sobre los que actúan simplemente no funcionan cuando te topas con un límite que te impide mover un método y te obliga a mover datos.
Es realmente así de simple. Si usa getters cuando no hay límite, terminará sin objetos reales. Todo comienza a tender a lo procesal. Que funciona tan bien como siempre.
La verdadera OOP no es algo que pueda difundir en todas partes. Solo funciona dentro de esos límites.
Esos límites no son muy delgados. Tienen código en ellos. Ese código no puede ser OOP. No puede ser funcional tampoco. No, este código tiene nuestros ideales despojados de él para que pueda lidiar con la dura realidad.
Michael Fetters llamó a este código fascia después de ese tejido conectivo blanco que mantiene juntas secciones de una naranja.
Esta es una forma maravillosa de pensarlo. Explica por qué está bien tener ambos tipos de código en la misma base de código. Sin esta perspectiva, muchos programadores nuevos se aferran a sus ideales con fuerza, luego se les rompe el corazón y abandonan estos ideales cuando alcanzan su primer límite.
Los ideales solo funcionan en su lugar apropiado. No renuncies a ellos solo porque no funcionan en todas partes. Úsalos donde trabajan. Ese lugar es la parte jugosa que protege la fascia.
Un ejemplo simple de un límite es una colección. Esto contiene algo y no tiene idea de qué es. ¿Cómo podría un diseñador de colección posiblemente mover la funcionalidad de comportamiento del objeto retenido a la colección cuando no tiene idea de lo que va a retener? No puedes Estás contra un límite. Por eso las colecciones tienen captadores.
Ahora, si lo supiera, podría mover ese comportamiento y evitar el estado de movimiento. Cuando lo sepas, deberías. Simplemente no siempre lo sabes.
Algunas personas simplemente llaman a esto ser pragmático. Y es. Pero es bueno saber por qué tenemos que ser pragmáticos.
Usted ha expresado que no quiere escuchar argumentos semánticos y parece estar abogando por poner "captadores sensibles" en todas partes. Estás pidiendo que se desafíe esta idea. Creo que puedo mostrar que la idea tiene problemas con la forma en que la ha enmarcado. Pero también creo que sé de dónde vienes porque he estado allí.
Si quieres captadores en todas partes, mira Python. No hay una palabra clave privada. Sin embargo, Python funciona bien. ¿Cómo? Usan un truco semántico. Nombran todo lo que debe ser privado con un guión bajo destacado. Incluso puede leerlo siempre que asuma la responsabilidad de hacerlo. "Todos somos adultos aquí", dicen a menudo.
Entonces, ¿cuál es la diferencia entre eso y simplemente poner getters en todo en Java o C #? Lo siento pero es semántica. La convención de subrayado de Pythons le indica claramente que está hurgando detrás de la puerta de los empleados. Golpea a los captadores en todo y pierdes esa señal. Con la reflexión, de todos modos, podría haberse despojado de lo privado y aún no haber perdido la señal semántica. Simplemente no hay un argumento estructural que hacer aquí.
Entonces, lo que nos queda es el trabajo de decidir dónde colgar el letrero de "solo empleados". ¿Qué se debe considerar privado? Lo llaman "captadores sensibles". Como he dicho, la mejor justificación para un captador es un límite que nos aleja de nuestros ideales. Eso no debería resultar en captadores de todo. Cuando da como resultado un captador, debe considerar mover el comportamiento aún más hacia la parte jugosa donde puede protegerlo.
Esta separación ha dado lugar a unos pocos términos. Un objeto de transferencia de datos o DTO no tiene comportamiento. Los únicos métodos son getters y algunas veces setters, a veces un constructor. Este nombre es desafortunado porque no es un objeto verdadero en absoluto. Los captadores y establecedores son realmente solo códigos de depuración que le dan un lugar para establecer un punto de interrupción. Si no fuera por esa necesidad, solo serían un montón de campos públicos. En C ++ solíamos llamarlos structs. La única diferencia que tenían de una clase de C ++ era que estaban predeterminados en público.
Los DTO son buenos porque puedes arrojarlos sobre un muro de límites y mantener tus otros métodos de forma segura en un objeto de comportamiento agradable y jugoso. Un verdadero objeto. Sin getters para violar es encapsulación. Mis objetos de comportamiento pueden comer DTO al usarlos como objetos de parámetro . A veces tengo que hacer una copia defensiva para evitar un estado mutable compartido . No esparzo DTOs mutables dentro de la parte jugosa dentro del límite. Los encapsulo. Los escondo Y cuando finalmente me encuentro con un nuevo límite, giro un nuevo DTO y lo tiro sobre la pared, lo que lo convierte en un problema para otra persona.
Pero desea proporcionar captadores que expresen su identidad. Bueno, felicidades, has encontrado un límite. Las entidades tienen una identidad que va más allá de su referencia. Es decir, más allá de su dirección de memoria. Por lo tanto, debe almacenarse en algún lugar. Y algo tiene que ser capaz de referirse a esto por su identidad. Un captador que expresa identidad es perfectamente razonable. Una pila de código que usa ese captador para tomar decisiones que la Entidad podría haber tomado no lo es.
Al final, no es la existencia de captadores lo que está mal. Son mucho mejores que los campos públicos. Lo malo es cuando se usan para fingir que estás orientado a objetos cuando no lo estás. Los captadores son buenos. Ser orientado a objetos es bueno. Los captadores no están orientados a objetos. Use getters para forjar un lugar seguro para orientarse a objetos.
fuente
clearly
? Si los datos me pasan de algo, por valor , claramente, mi objeto ahora posee los datos, pero por semántica, todavía no lo hace, en este punto, simplemente obtuvo los datos de otra persona. Luego debe realizar operaciones para transformar esos datos, haciéndolos suyos, en el momento en que se tocan los datos, debe realizar cambios semánticos: ¿Recibió los datos como$data_from_request
y ahora los operó? Nómbralo$changed_data
. ¿Desea exponer estos datos a otros? Cree un captadorgetChangedRequestData
, entonces, la propiedad está claramente establecida. ¿O es eso?Getters viola el Principio de Hollywood ("No nos llames, te llamaremos")
El Principio de Hollywood (también conocido como Inversión de control) establece que no se llama al código de la biblioteca para hacer las cosas; más bien, el marco llama a tu código. Debido a que el marco controla las cosas, no es necesario transmitir su estado interno a sus clientes. No necesitas saberlo.
En su forma más insidiosa, violar el Principio de Hollywood significa que está utilizando un captador para obtener información sobre el estado de una clase y luego tomar decisiones sobre qué métodos recurrir a esa clase en función del valor que obtenga. Es violación de la encapsulación en su máxima expresión.
Usar un getter implica que necesitas ese valor, cuando en realidad no lo necesitas .
Es posible que realmente necesite esa mejora de rendimiento
En casos extremos de objetos livianos que deben tener el máximo rendimiento posible, es posible (aunque extremadamente improbable) que no pueda pagar la penalización de rendimiento muy pequeña que impone un getter. Esto no sucederá el 99.9 por ciento de las veces.
fuente
Generator
objeto que recorre todos misItems
objetos, luego llamagetName
a cada unoItem
para hacer algo más. ¿Cuál es el problema con esto? Luego, a cambio,Generator
escupe cadenas formateadas. Esto está dentro de mi marco, para lo cual tengo una API además que las personas pueden usar para ejecutar lo que proporcionan los usuarios pero sin tocar el marco.What is the issue with this?
Ninguno que pueda ver. Eso es esencialmente lo que hace unamap
función. Pero esa no es la pregunta que hiciste. Básicamente, usted preguntó "¿Hay alguna condición bajo la cual un captador pueda ser desaconsejable"? Respondí con dos, pero eso no significa que abandones a los setters por completo.Detalles de implementación de fugas de Getters y abstracción de ruptura
Considerar
public int millisecondsSince1970()
Sus clientes pensarán "oh, esto es un int" y habrá un millón de llamadas asumiendo que es un int , haciendo una comparación matemática de fechas enteras, etc. Cuando se dé cuenta de que necesita mucho tiempo , habrá mucho de código heredado con problemas. En un mundo Java, agregarías
@deprecated
a la API, todos lo ignorarán y estarás atrapado manteniendo código obsoleto y defectuoso. :-( (¡Supongo que otros idiomas son similares!)En este caso en particular, no estoy seguro de cuál podría ser una mejor opción, y la respuesta @candiedorange es completamente acertada, hay muchas ocasiones en las que necesita captadores, pero este ejemplo ilustra las desventajas. Cada captador público tiende a "encerrarlo" en una determinada implementación. Úselos si realmente necesita "cruzar los límites", pero úselos lo menos posible y con cuidado y previsión.
fuente
timepoint
ytimespan
respectivamente. (epoch
es un valor particular detimepoint
.) En estas clases, proporcionan captadores comogetInt
orgetLong
ogetDouble
. Si las clases que manipulan el tiempo se escriben de manera que (1) deleguen la aritmética relacionada con el tiempo a estos objetos y métodos, y (2) proporcionen acceso a estos objetos de tiempo a través de captadores, entonces el problema se resuelve, sin necesidad de deshacerse de los captadores .Creo que la primera clave es recordar que los absolutos siempre están mal.
Considere un programa de libreta de direcciones, que simplemente almacena la información de contacto de las personas. Pero, hagámoslo bien y dividámoslo en capas de controlador / servicio / repositorio.
Habrá una
Person
clase que contiene datos reales: nombre, dirección, número de teléfono, etc.Address
y tambiénPhone
son clases, por lo que podemos usar el polimorfismo más adelante para admitir esquemas no estadounidenses. Las tres clases son objetos de transferencia de datos , por lo que no serán más que getters y setters. Y eso tiene sentido: no van a tener ninguna lógica en ellos más allá de anularequals
ygetHashcode
; pedirles que le den una representación de la información completa de una persona no tiene sentido: ¿es para una presentación HTML, una aplicación GUI personalizada, una aplicación de consola, un punto final HTTP, etc.?Ahí es donde intervienen el controlador, el servicio y el repositorio. Todas esas clases serán lógicas y ligeras, gracias a la inyección de dependencia.
Al controlador se le inyectará un servicio, que expondrá métodos como
get
ysave
, los cuales tomarán algunos argumentos razonables (por ejemplo,get
podrían tener anulaciones para buscar por nombre, buscar por código postal o simplemente obtener todo;save
probablemente tomará un soloPerson
y guardarlo). ¿Qué captadores tendría el controlador? Ser capaz de obtener el nombre de la clase concreta puede ser útil para iniciar sesión, pero ¿qué estado mantendrá además del estado que se le ha inyectado?Del mismo modo, la capa de servicio recibirá un depósito inyectado, por lo que en realidad puede obtener y persistir
Person
, y puede tener alguna "lógica de negocios" (por ejemplo, tal vez vamos a requerir que todos losPerson
objetos tengan al menos una dirección o teléfono número). Pero, de nuevo: ¿qué estado tiene ese objeto además de lo que se inyecta? De nuevo, ninguno.La capa de repositorio es donde las cosas se ponen un poco más interesantes: va a establecer una conexión con una ubicación de almacenamiento, por ejemplo. un archivo, una base de datos SQL o un almacén en memoria. Aquí es tentador agregar un captador para obtener el nombre de archivo o la cadena de conexión SQL, pero ese es el estado que se ha inyectado en la clase. ¿El repositorio debe tener un captador para saber si está o no conectado correctamente a su almacén de datos? Bueno, tal vez, pero ¿de qué sirve esa información fuera de la clase del repositorio en sí? La clase del repositorio en sí debería poder intentar volver a conectarse a su almacén de datos si es necesario, lo que sugiere que la
isConnected
propiedad ya tiene un valor dudoso. Además, unaisConnected
propiedad probablemente va a estar equivocada justo cuando más debería ser correcta: verificarisConnected
antes de intentar obtener / almacenar datos no garantiza que el repositorio todavía esté conectado cuando se realiza la llamada "real", por lo que no elimina la necesidad de manejo de excepciones en algún lugar (hay argumentos sobre dónde debería ir eso, más allá de El alcance de esta pregunta).Considere también las pruebas unitarias (está escribiendo pruebas unitarias, ¿verdad?): El servicio no esperará que se inyecte una clase específica, sino que esperará que se inyecte una implementación concreta de una interfaz. Eso permite que la aplicación en su totalidad cambie el lugar donde se almacenan los datos sin tener que hacer nada más que cambiar el repositorio que se inyecta en el servicio. También permite que el servicio se pruebe unitariamente inyectando un repositorio simulado. Esto significa pensar en términos de interfaces en lugar de clases. ¿La interfaz del repositorio expondría algún getter? Es decir, ¿hay algún estado interno que sería: común a todos los repositorios, útil fuera del repositorio y no inyectado en el repositorio? Sería difícil pensar en mí mismo.
TL; DR
En conclusión: aparte de las clases cuyo único propósito es transportar datos, nada más en la pila tiene un lugar para colocar un getter: el estado se inyecta o es irrelevante fuera de la clase trabajadora.
fuente
Miembros mutables.
Si tiene una colección de cosas en un objeto que expone a través de un captador, posiblemente se exponga a errores asociados con las cosas incorrectas que se agregan a la colección.
Si tiene un objeto mutable que está exponiendo a través de un captador, potencialmente se está abriendo al estado interno de su objeto que se está cambiando de una manera que no espera.
Un ejemplo realmente malo sería un objeto que cuente de 1 a 100 exponiendo su valor Actual como referencia mutable. Esto permite que un objeto de terceros cambie el valor de Current de tal manera que el valor pueda estar fuera de los límites esperados.
Esto es principalmente un problema con la exposición de objetos mutables. Exponer estructuras u objetos inmutables no es un problema en absoluto, ya que cualquier cambio en ellos cambiará una copia en su lugar (generalmente, ya sea por una copia implícita en el caso de una estructura o por una copia explícita en el caso de un objeto inmutable).
Usar una metáfora que pueda hacer que sea más fácil ver la diferencia.
Una flor tiene una serie de cosas que son únicas al respecto. Tiene una
Color
y tiene una serie de pétalos (NumPetals). Si estoy observando esa flor, en el mundo real, puedo ver claramente su color. Entonces puedo tomar decisiones basadas en ese color. Por ejemplo, si el color es negro, no le des a la novia, pero si el color es rojo, dale a la novia. En nuestro modelo de objetos, el color de la flor estaría expuesto como captador en el objeto de la flor. Mi observación de ese color es importante para las acciones que realizaré, pero no tiene ningún impacto en el objeto de la flor. No debería poder cambiar el color de esa flor. Igualmente, no tiene sentido ocultar la propiedad de color. Una flor generalmente no puede evitar que las personas observen su color.Si tiño la flor, debería llamar al método ReactToDye (Color dyeColor) en la flor, que cambiará la flor de acuerdo con sus reglas internas. Luego puedo consultar la
Color
propiedad nuevamente y reaccionar ante cualquier cambio después de que se haya llamado al método ReactToDye. Sería un error para mí modificar directamenteColor
la flor y si pudiera, entonces la abstracción se ha roto.Algunas veces (con menos frecuencia, pero con la frecuencia suficiente como para mencionar) los configuradores tienen un diseño OO bastante válido. Si tengo un objeto de Cliente, es bastante válido exponer un setter para su dirección. Si llama a eso
setAddress(string address)
oChangeMyAddressBecauseIMoved(string newAddress)
simplementestring Address { get; set; }
es una cuestión de semántica. El estado interno de ese objeto debe cambiar y la forma adecuada de hacerlo es establecer el estado interno de ese objeto. Incluso si requiero un registro histórico de las direcciones en las queCustomer
ha vivido, puedo usar el setter para cambiar mi estado interno de manera apropiada para hacerlo. En este caso, no tiene sentidoCustomer
que sea inmutable y no hay mejor manera de cambiar una dirección que proporcionar un configurador para que lo haga.No estoy seguro de quién sugiere que Getters y setters son malos o rompen paradigmas orientados a objetos, pero si lo hacen, probablemente lo estén haciendo en respuesta a una característica específica del lenguaje que utilizan. Tengo la sensación de que esto surgió de la cultura de Java "todo es un objeto" (y posiblemente se extendió a otros lenguajes). El mundo .NET no tiene esta discusión en absoluto. Getters and Setters son una función de lenguaje de primera clase que no solo utilizan las personas que escriben aplicaciones en el idioma, sino también la propia API de idioma.
Debe tener cuidado al usar getters. No desea exponer los datos incorrectos ni desea exponer los datos de manera incorrecta (es decir, objetos mutables, referencias a estructuras que pueden permitir que un consumidor modifique el estado interno). Pero sí desea modelar sus objetos para que los datos se encapsulen de tal manera que sus objetos puedan ser utilizados por consumidores externos y protegidos del mal uso por esos mismos consumidores. A menudo eso requerirá captadores.
Para resumir: Getters y Setters son herramientas válidas de diseño orientado a objetos. No se deben usar con tanta libertad que expongan todos los detalles de la implementación, pero tampoco desea usarlos con tanta moderación que los consumidores de un objeto no puedan usar el objeto de manera eficiente.
fuente
La mantenibilidad se romperá.
En cualquier momento (debería decir casi siempre) ofrece un captador que básicamente regala más de lo que se le pidió. Esto también significa que debe mantener más de lo que se le pidió, por lo tanto, reduce la capacidad de mantenimiento sin ningún motivo real.
Vayamos con el
Collection
ejemplo de @ candied_orange . Al contrario de lo que escribeCollection
, no debería tener un captador. Las colecciones existen por razones muy específicas, sobre todo para iterar sobre todos los elementos. Esta funcionalidad no es una sorpresa para nadie, por lo que debe implementarse en elCollection
, en lugar de presionar este caso de uso obvio en el usuario, utilizando ciclos for y getters o cualquier otra cosa.Para ser justos, algunos límites hacen captadores necesidad. Esto sucede si realmente no sabes para qué se usarán. Por ejemplo, hoy en día puede obtener el seguimiento de pila de la
Exception
clase de Java , porque la gente quería usarlo por todo tipo de razones curiosas que los escritores no pudieron o no pudieron imaginar.fuente
Collection
s simultáneamente, como en(A1, B1), (A2, B2), ...
. Esto requiere una implementación de "zip". ¿Cómo se implementa "zip" si la función de iteración se implementa en uno de los dosCollection
? ¿Cómo se accede al elemento correspondiente en el otro?Collection
tiene que implementarsezip
consigo mismo, dependiendo de cómo se escriba la biblioteca. Si no es así, lo implementa y envía una solicitud de extracción al creador de la biblioteca. Tenga en cuenta que acepté que algunos límites necesitan getters a veces, mi verdadero problema es que los getters están en todas partes hoy en día.Collection
objeto, al menos para su propia implementación dezip
. (Es decir, el comprador tiene que tener visibilidad de paquete por lo menos.) Y ahora dependerá del creador de la biblioteca de aceptar su solicitud de extracción ...Collection
obviamente, poder acceder a su propio estado interno, o para ser más precisos cualquierCollection
instancia puede acceder el estado interno de cualquier otro. Es del mismo tipo, no se necesitan captadores.