¿Qué pasa si los globales tienen sentido?

10

Tengo un valor que muchos objetos necesitan. Por ejemplo, una aplicación financiera con diferentes inversiones como objetos, y la mayoría de ellas necesitan la tasa de interés actual.

Esperaba encapsular mi "entorno financiero" como un objeto, con la tasa de interés como propiedad. Pero, los objetos hermanos que necesitan ese valor no pueden alcanzarlo.

Entonces, ¿cómo comparto valores entre muchos objetos sin acoplar demasiado mi diseño? Obviamente estoy pensando en esto mal.

Greg
fuente
2
¿La tasa de interés es fija para la duración de sus cálculos o está haciendo algo así como una simulación en la que puede variar entre tiempos?
James
Es más como una simulación: puede cambiar durante la ejecución.
Greg
En ese caso, ¿cada inversión realmente necesita ahorrar la tasa de interés o puede recibirla a través de un parámetro a una updatefunción que se llama en cada paso de tiempo? ¿Puedes publicar en pseudocódigo cómo funciona tu simulación?
James
3
Usted es uno de los temas correctos, Singletones un global con azúcar sintáctico OO y es una solución terrible que combina estrechamente su código en algunas de las peores formas posibles. ¡Lee este artículo una y otra vez hasta que lo entiendas!
La tasa de interés es como una serie temporal, que es una función que toma DateTimecomo entrada y devuelve un número como salida.
rwong

Respuestas:

14

Tengo un valor que muchos objetos necesitan.

Este es un olor de diseño. Es raro que muchos objetos necesiten saber sobre algo. Dicho esto, la tasa de interés actual es un ejemplo bastante bueno de circunstancias excepcionales. Una cosa de la que preocuparse es que rara vez existe la tasa de interés. Diferentes instrumentos financieros utilizan diferentes tasas. Como mínimo, las diferentes configuraciones regionales utilizan diferentes tasas 'estándar'. Además, para ayudar en las pruebas y los informes, generalmente querrá pasar una tasa ya que no desea usar la tasa actual allí. Desea utilizar la tasa 'qué pasaría si' o 'a partir de la fecha del informe'.

Entonces, ¿cómo comparto valores entre muchos objetos sin acoplar demasiado mi diseño?

Al compartirlos, no hacer que todos se refieran a una sola instancia. Pasar lo mismo sigue siendo un acoplamiento, pero no un exceso , ya que se necesita algo así como la tasa de interés actual como entrada para una variedad de cálculos.

Telastyn
fuente
17
El problema con circunstancias excepcionales es que en el software del mundo real no son tan excepcionales.
mattnz
Creo que este es un grave malentendido. Nuestros algoritmos existen en diferentes contextos simultáneamente. El primero es el contexto global. Luego "este" contexto para lenguajes orientados a objetos. Contexto de sesión en un servicio web, contexto de transacción en un entorno de base de datos, ventana principal para una interfaz gráfica de usuario, ... y hay piezas de información que pertenecen a estos contextos. Deben "pasar el rato" y estar disponibles, el mismo conjunto (objetos) para cualquier persona en el mismo contexto. Esto está bien. El problema es resolver esto para cada objeto, no creando un servicio de contexto o usando un marco, como Spring en Java.
Lorand Kedves
3
No hay nada excepcional sobre la tasa de interés actual. Hay muchos ejemplos de artículos del mundo real que tienen un valor: Límite de velocidad en carretera abierta, nivel aceptable de alcohol en sangre, tasa impositiva de GST (o IVA), por nombrar algunos. Esa es la diferencia entre Ciencia e Ingeniería: los ingenieros resuelven los problemas del mundo real de hoy, los científicos sueñan con el día en que el mundo real se adapte a las cajas de perfección y resuelva esos problemas.
mattnz
1
Elegí esto como la respuesta porque es simple y no se basa en una década de experiencia OOP para asimilar. MUCHAS gracias a todos los encuestados. Tuve un día completo de lectura gracias a las muchas referencias, pero aún estoy un poco perplejo. Para una pregunta tan simple, me sorprendió la variedad y la emoción detrás de las respuestas. Sigo convencido de que a veces hay una fuente central de datos globales pero variables que mejor sirve un Singleton. No creo que uno deba pasar punteros arriba y abajo de una jerarquía de objetos solo para evitar un Singleton. Gracias de nuevo a todos.
Greg
@mattnz, el problema es que cada uno de sus ejemplos es variable en los casos en que distribuye su programa a múltiples bases de usuarios que pueden abarcar empresas, estados o países. Todos ellos también pueden ser variables a lo largo del tiempo.
Dan Lyons
10

En este caso particular, usaría el patrón Singleton . El entorno financiero sería el objeto que todas las otras bibliotecas de clases conocen, pero sería instanciado por Singleton. Lo ideal sería enviar ese objeto instanciado a las diversas bibliotecas de clases.

Por ejemplo:

  • Capa de servicio (biblioteca de clases): crea una instancia del objeto FinancialEnvironment a través de un singleton
  • Capa de lógica de negocios (biblioteca de clases): acepta el objeto FinancialEnvironment de la capa de servicio
  • Capa de acceso a datos (biblioteca de clases): acepta el objeto FinancialEnvironment de la capa de servicio (o, según su arquitectura, la capa de lógica de negocios). O tal vez el Singleton invoca la capa de acceso a datos para obtener información, como la tasa de interés, de un repositorio (base de datos / servicio web / servicio WCF).
  • Biblioteca de clases de entidades (o DTO si quiere llamarlo así): dónde vive el objeto FinancialEnvironment. Todas las demás bibliotecas de clases tienen una referencia a la biblioteca de clases Entidades.

Las otras clases solo están vinculadas entre sí a través de la biblioteca de clases Entidades, aceptan un objeto FinancialEnvironment instanciado. No les importa cómo se creó, solo lo hace la capa de servicio, todo lo que quieren es la información. El singleton también podría ser lo suficientemente inteligente como para almacenar varios objetos del entorno financiero, dependiendo de las reglas para el local, como señaló @Telastyn.

En una nota al margen, no soy un gran admirador del Patrón Singleton, lo considero un olor a código, ya que se puede usar mal con mucha facilidad. Pero en algunos casos lo necesitas.

Actualizar:

Si absolutamente, debe tener una variable global, entonces implementar el Patrón Singleton como se describió anteriormente funcionaría. Sin embargo, no soy un gran admirador de esto, y según los comentarios de mi publicación original, muchas otras personas tampoco lo son. Como algo tan volátil como una tasa de interés, un Singleton puede no ser la mejor solución. Los Singleton funcionan mejor cuando la información no cambia. Por ejemplo, usé un Singleton en una de mis aplicaciones para crear instancias de contadores de rendimiento. Porque si cambian, entonces debe tener una lógica para manejar los datos que se actualizan.

Si fuera un apostador, apostaría que la tasa de interés se almacenó en algún lugar de una base de datos, o se recuperó a través de un servicio web. En ese caso, se recomendaría un Repositorio (capa de acceso a datos) para recuperar esa información. Para evitar viajes innecesarios a la base de datos (no estoy seguro con qué frecuencia cambian las tasas de interés u otra información en la clase de Información Financiera), se podría utilizar el almacenamiento en caché. En el mundo C #, la biblioteca Caching Application Block de Microsoft funciona muy bien.

Lo único que cambiaría del ejemplo anterior, serían las diversas clases en la capa de servicio que necesitan que FinancialInformation recupere de la capa de acceso a datos en lugar de que Singleton instancia el objeto.

bwalk2895
fuente
8
Ugh Hacer del mundo un singleton no lo hace menos maloliente. En todo caso, te has restringido mucho más.
Telastyn
3
@DavidCowden no importa si no son complejos de implementar; ellos fubarán tu diseño peor que los globales. Son globales y aplican restricciones (innecesarias) de que solo tienes una.
Telastyn
44
Iba a publicar un comentario sarcástico que decía "conviértalo en un singleton y pasará de la mala práctica a la mejor práctica", pero luego vi que ya era una respuesta aceptada y votada. ¡Muy agradable!
Kevin
44
Singletones un producto global con azúcar sintáctico OO y una muleta para los vagos y débiles. Singleton/global¡es la peor manera absoluta de acoplar estrechamente su código a algo que será un cáncer más adelante cuando se dé cuenta de lo colosalmente mala idea que era y por qué todos dicen que lo son!
44
@Telastyn: Es una realidad desafortunada que los diseños más perfectos una vez que abandonan el mundo perfectamente ordenado del diseño de software teórico y se unen al mundo real, se vuelvan fubar.
mattnz
4

¿Archivos de configuración?

Si tiene valores que se usan "globalmente", colóquelos en un archivo de configuración. Luego, cada sistema y subsistema puede hacer referencia a esto y extraer las teclas necesarias, hacerlas de solo lectura.

Noche oscura
fuente
Entonces, ¿desea que el usuario actualice un archivo de configuración cada vez que cambie la tasa de interés?
Caleb
2
Por qué no? depende de la "variable", por supuesto, las cosas que cambian con frecuencia deben colocarse dentro de un almacén de datos CENTRALIZADO.
Darknight
1

Estoy hablando de la experiencia de alguien que tiene aproximadamente un mes de mantenimiento en un proyecto de buen tamaño (~ 50k LOC) que acabamos de lanzar.

Puedo decirte que probablemente no quieras un objeto global. La introducción de ese tipo de cruft ofrece muchas más oportunidades de abuso de lo que ayuda.

Mi sugerencia inicial es que si tienes varias clases diferentes que necesitan una tasa de interés actual, entonces probablemente solo quieras que implementen IInterestRateConsumeralgo o algo. Dentro de esa interfaz tendrás un SetCurrentInterestRate(double rate)(o lo que tenga sentido), o tal vez solo una propiedad.

Pasar una tasa de interés no es en realidad un acoplamiento: si su clase necesita una tasa de interés, eso es parte de su API. Solo se acopla si una de sus clases comienza a preocuparse por cómo exactamente la otra clase usa esa tasa de interés.

Wayne Werner
fuente
Pasar una tasa de interés es un acoplamiento, simplemente no es un mal acoplamiento.
vaughandroid
1

Martin Fowler tiene un artículo que habla brevemente sobre cómo refactorizar un global estático en algo más flexible. Básicamente, lo convierte en un singleton y luego modifica el singleton para que admita anular la clase de la instancia con una subclase (y, si es necesario, mueva la lógica que crea la instancia a una clase separada que puede subclasificarse, lo que haría si crea la instancia de superclase, reemplazarla más tarde es un problema).

Por supuesto, debe sopesar los problemas con los singletons (incluso los singletons sustituibles) frente al dolor de pasar el mismo objeto a todas partes.

En cuanto al objeto "entorno financiero", es conveniente programar en el primer paso, pero cuando haya terminado, habrá agregado algunas dependencias adicionales. Las clases que solo necesitan una tasa de interés ahora solo funcionan cuando se pasa un objeto del entorno financiero, lo que dificultará su reutilización cuando no tenga un objeto del entorno financiero por ahí. Así que desalentaría pasarlo ampliamente.

psr
fuente
0

¿Por qué no poner los datos de la tasa de interés en un caché central?

Puede usar una de varias bibliotecas de caché, lo que mejor se adapte a sus necesidades, algo como memcached resuelve todos sus problemas de concurrencia y gestión de código y permitirá que su aplicación se adapte a múltiples procesos.

O vaya por completo y guárdelos en una base de datos, que le permitirá escalar a múltiples servidores.

James Anderson
fuente
0

En tales situaciones, he introducido (reutilizado) con éxito el término "contexto" con a veces múltiples capas.

Esto significa un singleton, por lo tanto, un almacén de objetos "global", desde el cual se puede solicitar este tipo de objetos. Los códigos que los requieren, incluyen el encabezado de la tienda y usan las funciones globales para obtener sus instancias de objeto (como ahora, el proveedor de tasas de interés).

La tienda puede ser:

  • estrictamente tipado: incluye los encabezados para todos los tipos servidos y, por lo tanto, puede crear accesores tipados, como InterestRate getCurrentInterestRate ();
  • o genérico: Object getObject (enum obType); y solo extienda la enumeración obType con los nuevos tipos (obtypeCurrentInterestRate).

Cuanto más grande es el sistema, más utilizable es la última solución, por un riesgo bastante pequeño de usar la enumeración incorrecta. Por otro lado, con idiomas que permiten declaraciones de tipo de reenvío, creo que puede usar los accesos escritos sin incluir todos los encabezados de la tienda.

Una nota más: puede tener varias instancias del mismo tipo de objeto para diferentes usos, como a veces un valor de idioma diferente para la GUI y para la impresión, registros globales y de nivel de sesión, etc., por lo que el nombre de enumeración / acceso NO debe reflejar el tipo real , pero el rol de la instancia solicitada (CurrentInterestRate).

En la implementación de la tienda, debe administrar los niveles de contexto y las colecciones de instancias de contexto. Un ejemplo simple es el servicio web, donde tiene el contexto global (una instancia para todas las solicitudes de ese objeto, problemático cuando se tiene una granja de servidores) y un contexto para cada sesión web. También puede tener contextos para cada usuario, que puede tener múltiples sesiones paralelas, etc. Con múltiples servidores, debe usar una especie de caché distribuida para tales cosas.

Cuando llega la solicitud, usted decide qué nivel de contexto es el objeto solicitado, obtenga ese contexto para la llamada. Si el objeto está allí, lo devuelve; si no, lo crea y lo almacena en ese nivel de contexto, y lo devuelve. Por supuesto, sincronice la sección de creación (y publíquela en el caché distribuido). La creación puede ser configurable como un complemento, mejor con lenguajes que permiten crear instancias de objetos por su nombre de clase (Java, Objective C, ...), pero puede hacerlo en C también con bibliotecas conectables que tienen funciones de fábrica.

Nota al margen: la persona que llama NO debe saber demasiado sobre sus propios contextos y el nivel de contexto del objeto solicitado. Motivos: 1: es fácil cometer errores (o "trucos ingeniosos") jugando con estos parámetros; 2: el nivel de contexto de lo solicitado podría cambiar más adelante. Principalmente conecto información de contexto al hilo, por lo que el almacén de objetos tiene la información sin parámetros adicionales de la solicitud.

Por otro lado, la solicitud puede contener una pista para la instancia: como obtener la tasa de interés para una fecha específica. Debe ser el mismo acceso "global", pero múltiples instancias dependiendo de la fecha (y llevando diferentes valores de fecha a la misma instancia entre cambios de velocidad), por lo que es aconsejable agregar un objeto "sugerencia" a la solicitud, utilizado por el instancia de fábrica y no la tienda; y un keyForHint para la fábrica, utilizado por la tienda. Puede agregar estas funciones más tarde, acabo de mencionar.

Para su caso, esto es una especie de exageración (solo se sirve un objeto a nivel global), pero para un código adicional bastante pequeño y simple en este momento, obtiene un mecanismo para requisitos adicionales, quizás más complejos.

Otra buena noticia: si estás en Java, obtienes este servicio de Spring sin pensar demasiado, solo quería explicarte en detalle.

Lorand Kedves
fuente
0

La razón para NO usar un global (o singleton) es que, aunque inicialmente espera tener solo un valor, a menudo es sorprendentemente útil poder usar el mismo código varias veces en el mismo programa, por ejemplo:

  • calcular qué pasaría si la tasa de interés fuera diferente
  • tener algunos componentes dependen de la tasa de interés de EE. UU. y algunos componentes dependen de la tasa de interés del Reino Unido

Convertiría la tasa de interés en un miembro de la clase de "instrumento financiero" y aceptaría que tiene que pasarla a cualquier clase de miembro (ya sea por cálculo o dándoles un puntero / gancho en la construcción).

Jack V.
fuente
0

Las cosas deben pasarse en mensajes, no leerse de una cosa global.

Tulains Córdova
fuente