proyecto - usar clases de otros paquetes java
¿Cómo organizar los paquetes(y prevenir los ciclos de dependencia)? (5)
He estado ejecutando algunas métricas en mi proyecto Java y aparentemente hay muchos ciclos de dependencia entre paquetes. Realmente no sabía cómo organizar las cosas en paquetes, así que simplemente hice lo que tenía sentido para mí, lo que aparentemente está mal.
Mi proyecto es un marco de red neuronal. Las redes neuronales tienen neuronas, que están conectadas entre sí con conexiones. Necesitan depender el uno del otro. Sin embargo, también hay diferentes tipos de neuronas, así que pensé que sería una buena idea colocarlas en su propio paquete de ''neuronas''. Obviamente, una Conexión no es una Neurona, por lo que no debería estar en el paquete, pero como se refieren entre sí, ahora tengo una dependencia circular.
Esto es solo un ejemplo, pero tengo más situaciones como esta. ¿Cómo manejas este tipo de situaciones?
Además, leí que las clases en un paquete más arriba en la jerarquía de paquetes no deben referirse a clases en paquetes que son más profundos. Esto significaría que una clase de NeuralNetwork en el paquete ''nn'' no puede referirse a la neurona en el paquete ''nn.neurons''. ¿Ustedes siguen este principio? ¿Y qué pasaría si moviera NeuralNetwork a ''nn.networks'' o algo así? En ese caso, se referiría a un paquete de hermanos en lugar de a un niño. ¿Eso es mejor práctica?
¿Cómo manejas este tipo de situaciones?
Las dependencias circulares no son inherentemente malas. De hecho, a veces esto puede ser un caso en el que "la cura es peor que la enfermedad": la extracción de una interfaz aumenta el nivel de complejidad de su código y agrega otra capa de direccionamiento indirecto. Probablemente no vale la pena por relaciones muy simples.
¿De qué tipo de tamaño de código estamos hablando? Si solo tiene 10-20 clases, probablemente no necesite (y no debería) sobre-organizar su código en paquetes por el simple hecho de hacerlo.
A medida que su proyecto crece, la primera distinción que desea hacer es separar el código de la interfaz de usuario del modelo de datos subyacente y la lógica. Tener una capa limpia y separada es crucial para poder realizar la prueba de unidad adecuada.
Si tiene problemas para deshacerse de las dependencias circulares, es probable que las clases sean realmente interdependientes y que residan en el mismo paquete.
Lograr las capas de abstracción correctas es probablemente uno de los aspectos más importantes al diseñar la estructura de código general.
En primer lugar, está legítimamente preocupado porque las dependencias circulares entre paquetes son malas. Los problemas que surgen de ella crecen en importancia con el tamaño del proyecto, pero no hay razón para abordar esta situación a tiempo.
Debe organizar sus clases colocando las clases que reutilice juntas en el mismo paquete. Entonces, si tiene, por ejemplo, AbstractNeuron y AbstractConnection, los colocaría en el mismo paquete. Si ahora tiene implementaciones HumanNeuron y HumanConnection, las colocará en el mismo paquete (llamado, por ejemplo, * .network.human). O bien, es posible que solo tenga un tipo de conexión, por ejemplo, BaseConnection y muchas Neuronas diferentes. El principio sigue siendo el mismo. Coloca BaseConnection junto con BaseNeuron. HumanNeuron en su propio paquete junto con HumanSignal, etc. VirtualNeuron junto con VirtualSignal, etc. Dice: "Obviamente, una conexión no es una neurona, por lo que no debería estar en el paquete ...". Esto no es tan obvio, ni correcto para ser exacto.
Usted dice que colocó todas sus neuronas en el mismo paquete. Esto tampoco es correcto, a menos que reutilice todas sus implementaciones juntas. Una vez más, eche un vistazo al esquema que describí anteriormente. O su proyecto es tan pequeño que puede colocarlo todo en el paquete individual, o comenzar a organizar los paquetes como se describe. Para más detalles, eche un vistazo al principio de reutilización común :
LAS CLASES EN UN PAQUETE SE REUTILIZAN JUNTAS. SI USTED REUTILIZA UNA DE LAS CLASES EN UN PAQUETE, LOS REUTILIZA TODOS.
La tarea antcontrib VerifyDesign le ayudará a hacer lo que quiere:
Por ejemplo, si hay tres paquetes en un árbol de origen
* biz.xsoftware.presentation * biz.xsoftware.business * biz.xsoftware.dataaccess
y, naturalmente, la presentación solo debe depender del paquete empresarial, y la empresa debe depender del acceso a datos. Si define su diseño de esta manera y se viola, la compilación fallará cuando se llame a la tarea de verificación de diseño. Por ejemplo, si creé una clase en biz.xsoftware.presentation y esa clase dependía de una clase en biz.xsoftware.dataaccess, la compilación fallaría. Esto asegura que el diseño realmente siga lo que está documentado (al menos hasta cierto punto). Esto es especialmente bueno con las construcciones automatizadas
Entonces, una vez que haya decidido cómo deben organizarse las cosas, puede hacer cumplir los requisitos en el momento de la compilación. También obtiene un control preciso para que pueda permitir que ciertos casos rompan estas "reglas". Así que puedes permitir algunos ciclos.
Dependiendo de cómo quiera hacer las cosas, puede encontrar que el paquete "utils" tenga sentido.
Para el caso particular que cites ... podría hacer algo como esto:
- el paquete nn contiene Nueron y Connection
- El paquete nn.neurons contiene las subclases de Nueron.
Neuron y Connection son conceptos de alto nivel utilizados en NeuralNetowrk, por lo que ponerlos todos juntos tiene sentido. Las clases Neuron y Connection pueden referirse entre sí, mientras que la clase Connection no tiene necesidad de conocer las subclases de Neuron.
No creo que las dependencias cíclicas como las que usted describe tengan que ser malas. Mientras los conceptos que son interdependientes se encuentren en el mismo nivel de abstracción y se relacionen con las mismas partes de la arquitectura, puede que no sea necesario ocultarse entre sí. Las neuronas y las conexiones se ajustan a este proyecto de ley en mi entendimiento.
Una forma común de reducir dichos acoplamientos es extraer interfaces y, posiblemente, colocarlos en un módulo separado. La simple organización por paquetes dentro de un solo proyecto no le permite ocultar los detalles de la implementación lo suficiente. Un patrón común que le permite ocultar implementaciones realmente es el siguiente:
Código de cliente ----> Interfaces <--- Implementación
En este patrón, oculta el módulo "Implementación" del código del cliente, lo que significa que el código en el módulo "Código del cliente" ni siquiera ve el código de implementación.
El anidamiento de paquetes sirve para varios propósitos: Algunos proyectos pueden tener un modelo de dominio que se organiza en paquetes. En este caso, los paquetes reflejan alguna agrupación del dominio, y las referencias pueden subir / bajar paquetes. Cuando se trata de cosas como la implementación de servicios, su patrón sugerido es bastante común y es bueno seguirlo. Cuanto más profunda sea la jerarquía de paquetes, más específica se cree que será la clase.