oop - smart - Tratar con estructuras de datos "globales" en un mundo orientado a objetos
smart contracts ethereum (9)
Es posible que desee pensar en modificar el requisito que muchos objetos necesitan saber sobre las mismas estructuras de datos. Una razón por la que no parece haber una forma limpia de OO de compartir datos es que el intercambio de datos no está muy orientado a objetos.
Tendrá que ver los detalles de su aplicación, pero la idea general es que un objeto sea responsable de los datos compartidos que proporcionan servicios a los otros objetos en función de los datos encapsulados en él. Sin embargo, estos servicios no deberían implicar dar a otros objetos las estructuras de datos, simplemente otorgarles a otros objetos las piezas de información que necesitan para cumplir con sus responsabilidades y realizar mutaciones en las estructuras de datos internamente.
Esta es una pregunta con muchas respuestas: me interesa saber qué es lo que otros consideran "mejores prácticas".
Considere la siguiente situación: tiene un programa orientado a objetos que contiene una o más estructuras de datos que son necesarias para muchas clases diferentes. ¿Cómo se hace que estas estructuras de datos sean accesibles?
Puede pasar referencias explícitamente, por ejemplo, en los constructores. Esta es la solución "adecuada", pero significa duplicar parámetros y variables de instancia en todo el programa. Esto dificulta cambios o adiciones a los datos globales.
Puede poner todas las estructuras de datos dentro de un solo objeto y pasar las referencias a este objeto. Esto puede ser un objeto creado solo para este propósito, o podría ser el objeto "principal" de su programa. Esto simplifica los problemas de (1), pero las estructuras de datos pueden tener algo que ver o no, y recopilarlas juntas en un solo objeto es bastante arbitrario.
Puede hacer que las estructuras de datos sean "estáticas". Esto le permite hacer referencia a ellos directamente desde otras clases, sin tener que pasar referencias. Esto evita completamente las desventajas de (1), pero claramente no es OO. Esto también significa que solo puede haber una sola instancia del programa.
Cuando hay muchas estructuras de datos, todas requeridas por muchas clases, tiendo a usar (2). Este es un compromiso entre OO-pureza y practicidad. ¿Qué hacen otras personas? (Por lo que vale, la mayoría procedo del mundo Java, pero esta discusión es aplicable a cualquier lenguaje OO).
¡Los datos globales no son tan malos como reclaman muchos puristas de OO!
Después de todo, al implementar las clases de OO generalmente usas una API para tu sistema operativo. ¡Qué diablos es esto si no es una gran cantidad de datos y servicios globales!
Si utiliza algunos elementos globales en su programa, simplemente amplía este entorno enorme que su implementación de clase ya puede ver en el sistema operativo con un poco de datos que son específicos del dominio de su aplicación.
Pasar punteros / referencias a todas partes a menudo se enseña en cursos y libros OO, académicamente suena bien. Pragmáticamente, a menudo es lo que se debe hacer, pero es un error seguir esta regla a ciegas y absolutamente. Para un programa de tamaño decente, puede terminar con un montón de referencias que se pasan por todo el lugar y puede resultar en un trabajo pesado innecesario.
Los proveedores de servicios / datos globalmente accesibles (abstraídos detrás de una buena interfaz, obviamente) son casi imprescindibles en una aplicación de tamaño decente.
No me gustan las soluciones propuestas:
- Está pasando por un montón de objetos de "contexto": las cosas que los usan no especifican qué campos o datos realmente les interesan.
- Vea aquí para una descripción del patrón del Objeto de Dios . Este es el peor de todos los mundos
- Simplemente no use objetos Singleton para nada. Parece que usted mismo ha identificado algunos de los posibles problemas
Opción 3) aunque no sea purista OO, tiende a ser la solución más razonable. Pero no convertiría a tu clase en un singleton; y use algún otro objeto como un "diccionario" estático para administrar esos recursos compartidos.
Realmente debo desanimarlo de usar la opción 3, lo que hace que los datos estén estáticos. Trabajé en varios proyectos en los que los primeros desarrolladores hicieron algunos datos básicos estáticos, solo para luego darme cuenta de que necesitaban ejecutar dos copias del programa, e incurrieron en una gran cantidad de trabajo al hacer que los datos no fueran estáticos y pusieran referencias cuidadosamente. en todo
Entonces, en mi experiencia, si lo haces 3), terminarás haciendo 1) al doble del costo.
Vaya por 1, y sea detallista sobre qué estructuras de datos hace referencia de cada objeto. No use "objetos de contexto", solo pase precisamente los datos necesarios. Sí, hace que el código sea más complicado, pero en el lado positivo, lo hace más claro: el hecho de que un FwurzleDigestionListener
tenga una referencia tanto a Fwurzle
como a DigestionTract
inmediatamente le da al lector una idea sobre su propósito.
Y, por definición, si el formato de datos cambia, también lo harán las clases que operan en él, por lo que deberá cambiarlos de todos modos.
Tiendo a usar 3) y tenga mucho cuidado con la sincronización y el bloqueo entre subprocesos. Estoy de acuerdo en que es menos OO, pero luego confiesas tener datos globales, lo cual es muy poco OO en primer lugar.
No se obsesione demasiado con si se apega exclusivamente a una metodología de programación u otra, encuentre una solución que se adapte a su problema. Creo que hay contextos perfectamente válidos para singletons (Logging por ejemplo).
Utilizo una combinación de tener un objeto global y pasar interfaces a través de constructores.
Desde el único objeto global principal (normalmente nombrado de acuerdo con el nombre o el nombre de su programa), puede iniciar otros globales (quizás con sus propios hilos). Esto le permite controlar la configuración de objetos de programa en el constructor principal de objetos y volver a abrirlos en el orden correcto cuando la aplicación se detiene en este destructor de objetos principales. Usar clases estáticas directamente hace que sea complicado inicializar / desinicializar cualquier recurso que estas clases utilicen de forma controlada. Este objeto global principal también tiene propiedades para acceder a las interfaces de diferentes subsistemas de su aplicación que varios objetos pueden querer aprovechar para hacer su trabajo.
También paso referencias a estructuras de datos relevantes en constructores de algunos objetos donde siento que es útil aislar esos objetos del resto del mundo dentro del programa cuando solo necesitan preocuparse por una pequeña parte de él.
Ya sea que un objeto agarre el objeto global y navegue por sus propiedades para obtener las interfaces que quiere o pasa, las interfaces que utiliza a través de su constructor es una cuestión de gusto e intuición. Cualquier objeto que implemente y que piense que podría reutilizarse en algún otro proyecto debería pasar estructuras de datos que debería usar a través de su constructor. Los objetos que toman el objeto global deberían tener más que ver con la infraestructura de su aplicación.
Los objetos que reciben interfaces que usan a través del constructor probablemente sean más fáciles de probar porque puede alimentarlos con una interfaz simulada y hacerles cosquillas para asegurarse de que devuelvan los argumentos correctos o interactúen correctamente con las interfaces simuladas. Para probar los objetos que acceden al objeto global principal, debe simular el objeto global principal para que cuando soliciten las interfaces (a menudo los llame) obtengan los objetos simulados apropiados y puedan contrastarlos.
Prefiero usar el patrón singleton como se describe en el libro GoF para estas situaciones. Un singleton no es lo mismo que cualquiera de las tres opciones descritas en la pregunta. El constructor es privado (o protegido) por lo que no se puede usar en ninguna parte. Utiliza una función get () (o lo que prefiera para llamarlo) para obtener una instancia. Sin embargo, la arquitectura de la clase singleton garantiza que cada llamada a get () devuelve la misma instancia.
Debemos tener cuidado de no confundir el Diseño Orientado a Objetos con la Implementación Orientada a Objetos. Con demasiada frecuencia, el término OO Design se usa para juzgar una implementación, al igual que, imho, está aquí.
Diseño
Si en tu diseño ves muchos objetos que hacen referencia exactamente al mismo objeto, eso significa muchas flechas. El diseñador debería sentir comezón aquí. Él debería verificar si este objeto se usa comúnmente, o si realmente es una utilidad (por ejemplo, una fábrica COM, un registro de algún tipo, ...).
A partir de los requisitos del proyecto, puede ver si realmente necesita ser un singleton (por ejemplo, "Internet"), o si el objeto se comparte porque es demasiado general, demasiado caro o lo que sea.
Implementación
Cuando se le pide que implemente un Diseño OO en un idioma OO, se enfrenta a muchas decisiones, como la que mencionó: ¿cómo debo implementar todas las flechas en el objeto usado en el diseño?
Ese es el punto donde se abordan las preguntas sobre ''miembro estático'', ''variable global'', ''clase de Dios'' y ''argumentos de una porción de función''.
La fase de Diseño debería haber aclarado si el objeto debe ser un singleton o no. La fase de implementación decidirá cómo se representará esta soltería en el programa.