Tengo una clase llamada Heading
que hace algunas cosas, pero también debería ser capaz de devolver el opuesto del valor del título actual, que finalmente debe usarse mediante la creación de una nueva instancia de la Heading
clase en sí.
Puedo tener una propiedad simple llamada reciprocal
para devolver el encabezado opuesto del valor actual y luego crear manualmente una nueva instancia de la clase de Encabezado, o puedo crear un método como createReciprocalHeading()
crear automáticamente una nueva instancia de la clase de Encabezado y devolverlo al usuario.
Sin embargo, uno de mis colegas me recomendó crear una propiedad de clase llamada reciprocal
que devuelva una nueva instancia de la clase a través de su método getter.
Mi pregunta es: ¿no es un antipatrón que una propiedad de una clase se comporte así?
Particularmente encuentro esto menos intuitivo porque:
- En mi opinión, una propiedad de una clase no debería devolver una nueva instancia de una clase, y
- El nombre de la propiedad, que es
reciprocal
, no ayuda al desarrollador a comprender completamente su comportamiento, sin obtener ayuda del IDE o verificar la firma del captador.
¿Estoy siendo demasiado estricto sobre lo que debe hacer una propiedad de clase o es una preocupación válida? Siempre he tratado de administrar el estado de la clase a través de sus campos y propiedades y su comportamiento a través de sus métodos, y no veo cómo esto se ajusta a la definición de la propiedad de la clase.
fuente
Heading
un tipo inmutable yreciprocal
devolver una nuevaHeading
es una buena práctica de "pozo de éxito". (con la advertencia de que las dos llamadasreciprocal
deben devolver "lo mismo", es decir, deben pasar la prueba de igualdad).Respuestas:
No es desconocido tener cosas como Copy () o Clone () pero sí, creo que tienes razón en preocuparte por esto.
tomar como ejemplo:
Sería bueno tener alguna advertencia de que la propiedad era un objeto nuevo cada vez.
También puede suponer que:
Sin embargo. Si su clase de encabezado fuera un tipo de valor inmutable, entonces podría implementar estas relaciones y recíproco podría ser un buen nombre para la operación
fuente
Copy()
yClone()
son métodos, no propiedades. Creo que el OP se refiere a los captadores de propiedades que devuelven nuevas instancias.String.Substring()
,Array.Reverse()
,Int32.GetHashCode()
, etc.If your heading class was an immutable value type
- Creo que debería agregar que los establecedores de propiedades en objetos inmutables deberían devolver siempre las nuevas instancias (o al menos si el nuevo valor no es el mismo que el valor anterior), porque esa es la definición de objetos inmutables. :)Una interfaz de una clase lleva al usuario de la clase a hacer suposiciones sobre cómo funciona.
Si muchos de estos supuestos son correctos y pocos son incorrectos, la interfaz es buena.
Si muchos de estos supuestos son incorrectos y pocos son correctos, la interfaz es basura.
Una suposición común sobre las Propiedades es que llamar a la función get es barato. Otra suposición común sobre las propiedades es que llamar a la función get dos veces seguidas devolverá lo mismo.
Puede solucionar este problema utilizando la coherencia para cambiar las expectativas. Por ejemplo, para una pequeña biblioteca 3D donde necesita
Vector
,Ray
Matrix
etc., puede hacer que los captadores comoVector.normal
yMatrix.inverse
sean casi siempre caros. Desafortunadamente, incluso si sus interfaces usan constantemente propiedades costosas, las interfaces serán, en el mejor de los casos, tan intuitivas como las que usaVector.create_normal()
yMatrix.create_inverse()
, sin embargo, no conozco ningún argumento sólido que pueda hacerse que el uso de propiedades crea una interfaz más intuitiva, incluso después de cambiar las expectativas.fuente
Las propiedades deberían regresar muy rápidamente, devolver el mismo valor con llamadas repetidas y obtener su valor no debería tener efectos secundarios. Lo que describe aquí no debe implementarse como una propiedad.
Su intuición es ampliamente correcta. Un método de fábrica no devuelve el mismo valor con llamadas repetidas. Devuelve una nueva instancia cada vez. Esto prácticamente lo descalifica como una propiedad. Tampoco es rápido si el desarrollo posterior agrega peso a la creación de la instancia, por ejemplo, dependencias de la red.
Las propiedades generalmente deben ser operaciones muy simples que obtienen / establecen una parte del estado actual de la clase. Las operaciones tipo fábrica no cumplen con este criterio.
fuente
DateTime.Now
propiedad se usa comúnmente y se refiere a un nuevo objeto. Creo que el nombre de una propiedad puede ayudar a eliminar la ambigüedad sobre si se devuelve o no el mismo objeto cada vez. Todo está en el nombre y cómo se usa.DateTime
es un tipo de valor y no tiene un concepto de identidad de objeto. Si entiendo correctamente, el problema es que no es determinista y devuelve un nuevo valor cada vez.DateTime.New
es estático y, por lo tanto, no puede esperar que represente el estado de un objeto.No creo que haya una respuesta agnóstica del idioma a esto, ya que lo que constituye una "propiedad" es una pregunta específica del idioma, y lo que espera una persona que llama de una "propiedad" también es una pregunta específica del idioma. Creo que la forma más fructífera de pensar en esto es pensar en cómo se ve desde el punto de vista de la persona que llama.
En C #, las propiedades se distinguen porque están capitalizadas (convencionalmente) (como los métodos) pero carecen de paréntesis (como las variables de instancia pública). Si ve el siguiente código, sin documentación, ¿qué espera?
Como un novato relativo de C #, pero uno que ha leído las Directrices de uso de propiedades de Microsoft , esperaría
Reciprocal
, entre otras cosas:Heading
claseReciprocalChanged
eventoDe estos supuestos, (3) y (4) son probablemente correctos (suponiendo que
Heading
es un tipo de valor inmutable, como en la respuesta de Ewan ), (1) es discutible, (2) es desconocido pero también discutible, y (5) es poco probable que tiene sentido semántico (aunque cualquier cosa que tenga un encabezado quizás debería tener unHeadingChanged
evento). Esto me sugiere que en una API de C #, "obtener o calcular el recíproco" no debe implementarse como una propiedad, pero especialmente si el cálculo es barato eHeading
inmutable, es un caso límite.(Sin embargo, tenga en cuenta que ninguna de estas preocupaciones tiene nada que ver con si llamar a la propiedad crea una nueva instancia , ni siquiera (2). Crear objetos en el CLR, en sí mismo, es bastante barato).
En Java, las propiedades son una convención de nomenclatura de métodos. Si yo veo
mis expectativas son similares a las anteriores (si se establecen de manera menos explícita): espero que la llamada sea barata, idempotente y carente de efectos secundarios. Sin embargo, fuera del marco de JavaBeans, el concepto de una "propiedad" no es tan significativo en Java, y particularmente cuando se considera una propiedad inmutable sin correspondencia
setReciprocal()
, lagetXXX()
convención está algo anticuada. Desde Effective Java , segunda edición (ya tiene más de ocho años):En una API contemporánea y más fluida, entonces, esperaría ver
- lo que nuevamente sugeriría que la llamada es barata, idempotente y carece de efectos secundarios, pero no diría nada sobre si se realiza un nuevo cálculo o si se crea un nuevo objeto. Esto esta bien; en una buena API, no me debería importar.
En Ruby, no existe tal cosa como una propiedad. Hay "atributos", pero si veo
No tengo forma inmediata de saber si estoy accediendo a una variable de instancia a
@reciprocal
través de unattr_reader
método simple o de acceso, o si estoy llamando a un método que realiza un cálculo costoso. Sin embargo, el hecho de que el nombre del método sea un nombre simplecalcReciprocal
sugiere, una vez más, que la llamada es al menos barata y probablemente no tiene efectos secundarios.En Scala, la convención de nomenclatura es que los métodos con efectos secundarios toman paréntesis y los métodos sin ellos no, pero
podría ser cualquiera de:
(Tenga en cuenta que Scala permite varias cosas que, sin embargo, no se recomiendan en la guía de estilo . Esta es una de mis principales molestias con Scala).
La falta de paréntesis me dice que la llamada no tiene efectos secundarios; el nombre, nuevamente, sugiere que la llamada debería ser relativamente barata. Más allá de eso, no me importa cómo me da el valor.
En resumen: conozca el lenguaje que está utilizando y sepa qué expectativas traerán otros programadores a su API. Todo lo demás es un detalle de implementación.
fuente
Como han dicho otros, es un patrón bastante común devolver instancias de la misma clase.
La nomenclatura debe ir de la mano con las convenciones de nomenclatura del lenguaje.
Por ejemplo, en Java probablemente esperaría que se llamara
getReciprocal();
Dicho esto, consideraría objetos mutables vs inmutables .
Con los inmutables , las cosas son bastante fáciles, y no dolerá si devuelve el mismo objeto o no.
Con los mutables , esto puede ser muy aterrador.
¿A qué se refiere b? ¿El recíproco del valor original de a? ¿O el recíproco del cambiado? Este podría ser un ejemplo demasiado corto, pero entiendes el punto.
En esos casos, busque un mejor nombre que comunique lo que sucede, por ejemplo,
createReciprocal()
podría ser una mejor opción.Pero realmente también depende del contexto.
fuente
getReciprocal()
, ya que "suena" como un captador de estilo JavaBeans "normal". Prefiere "createXXX ()" o posiblemente "CalculateXXX ()" que indica o insinúa a otros programadores que algo diferente está sucediendoEn aras de la responsabilidad única y la claridad, tendría una ReverseHeadingFactory que tomó el objeto y devolvió su inverso. Esto aclararía que se estaba devolviendo un nuevo objeto y significaría que el código para producir el reverso estaba encapsulado lejos de otro código.
fuente
Depende. Por lo general, espero que las propiedades devuelvan cosas que son parte de una instancia, por lo que devolver una instancia diferente de la misma clase sería un poco inusual.
Pero, por ejemplo, una clase de cadena podría tener propiedades "firstWord", "lastWord", o si maneja Unicode "firstLetter", "lastLetter", que serían objetos de cadena completa, por lo general, los más pequeños.
fuente
Como las otras respuestas cubren la parte de Propiedad, solo abordaré su # 2 sobre
reciprocal
:No use nada más que
reciprocal
. Es el único término correcto para lo que está describiendo (y es el término formal). No escriba incorrecto del software para guardar los desarrolladores de aprender el dominio que se está trabajando. En la navegación, los términos tienen significados muy específicos y el uso de algo tan aparentemente inocuo comoreverse
oopposite
podría dar lugar a confusión en ciertos contextos.fuente
Lógicamente, no, no es propiedad de un encabezado. Si lo fuera, también se podría decir que un número negativo es propiedad de ese número y, francamente, eso haría que "propiedad" pierda todo significado, casi todo sería una propiedad.
Recíproco es una función pura de un encabezado, lo mismo que negativo de un número es una función pura de ese número.
Como regla general, si configurarlo no tiene sentido, probablemente no debería ser una propiedad. Definirlo todavía se puede rechazar, por supuesto, pero agregar un setter debería ser teóricamente posible y significativo.
Ahora, en algunos idiomas, podría tener sentido tenerlo como una propiedad como se especifica en el contexto de ese idioma de todos modos, si trae algunas ventajas debido a la mecánica de ese idioma. Por ejemplo, si desea almacenarlo en una base de datos, probablemente sea sensato convertirlo en propiedad si permite el manejo automático mediante el marco ORM. O tal vez es un lenguaje en el que no hay distinción entre una propiedad y una función miembro sin parámetros (no conozco dicho lenguaje, pero estoy seguro de que hay algunos). Luego, corresponde al diseñador de API y al documentador distinguir entre funciones y propiedades.
Editar: para C # específicamente, miraría las clases existentes, especialmente las de la Biblioteca estándar.
BigInteger
yComplex
estructuras. Pero son estructuras, y solo tienen funciones estáticas, y todas las propiedades son de diferente tipo. Así que no hay mucha ayuda de diseño para tuHeading
clase.Vector
clase , que tiene muy pocas propiedades, y parece estar bastante cerca de la tuyaHeading
. Si sigues esto, entonces tendrías una función para recíproco, no una propiedad.Es posible que desee ver las bibliotecas realmente utilizadas por su código o las clases similares existentes. Intenta mantenerte constante, eso suele ser lo mejor, si una forma no es claramente correcta y otra equivocada.
fuente
Muy interesante pregunta de hecho. No veo ninguna violación de SOLID + DRY + KISS en lo que estás proponiendo, pero, aun así, huele mal.
Un método que devuelve una instancia de la clase se llama constructor , ¿verdad? entonces estás creando una nueva instancia a partir de un método no constructor. No es el fin del mundo, pero como cliente, no esperaría eso. Esto generalmente se hace en circunstancias específicas: una fábrica o, a veces, un método estático de la misma clase (útil para singletons y ... ¿para programadores no tan orientados a objetos?)
Además: ¿qué sucede si invocas
getReciprocal()
el objeto devuelto? obtienes otra instancia de la clase, ¡que podría ser una copia exacta de la primera! ¿Y si invocasgetReciprocal()
en ESO? ¿Objetos en expansión a alguien?De nuevo: ¿qué pasa si el invocador necesita el valor recíproco (como escalar, quiero decir)?
getReciprocal()->getValue()
? Estamos violando la Ley de Deméter por pequeñas ganancias.fuente