Pregunta

Una cosa con la que me he encontrado varias veces es una clase de servicio (como un servicio de JBoss) que se ha vuelto demasiado grande debido a las clases internas de ayuda. Todavía tengo que encontrar una buena manera de romper la clase. Estos ayudantes suelen ser hilos. Aquí hay un ejemplo:

/** Asset service keeps track of the metadata about assets that live on other
 * systems. Complications include the fact the assets have a lifecycle and their
 * physical representation lives on other systems that have to be polled to find
 * out if the Asset is still there. */
public class AssetService
{
  //...various private variables
  //...various methods

  public AssetService()
  {
    Job pollerJob = jobService.schedule( new AssetPoller() );
    Job lifeCycleJob = jobService.schedule( AssetLifecycleMonitor() );
  }

  class AssetPoller
  {
    public void run()
    { 
      // contact remote systems and update this service's private variables that
      // track the assets.
    }
  }

  class AssetLifecycleMonitor
  {
    public void run()
    {
      // look for assets that have meet criteria for a lifecycle shift
      // and update this service's private variables as relevant.
    }
  }
}

Entonces, lo que puede suceder si tengo un par de ayudantes y todos son complejos, es que el archivo de clase en general puede ser realmente grande. Me gustan las clases internas porque deja claro que las clases son propiedad del servicio y existen solo para ayudar a ese servicio. He intentado dividir las clases y pasar el servicio para padres como referencia, que funciona principalmente, pero las cosas que no me gustan son:  

  • Termino exponiendo los accesores de nivel de paquete para que las clases desglosadas puedan acceder a las variables, mientras que antes no expuse a los definidores en absoluto ya que las clases internas tenían acceso directo.  
  • Además, las cosas se vuelven un poco más largas ya que estoy llamando constantemente a los accesores en lugar de a las variables subyacentes. Una liendre menor, concedida.  
  • Los métodos de conveniencia (por ejemplo, checkAssetIsValid () o algunos de ellos) necesitan una exposición a nivel de paquete ahora para que las clases auxiliares puedan llamarlos, donde antes, como clases internas, podrían ser privadas.  
  • Peor aún, necesito pasar la clase de implementación del servicio a los constructores de las clases de ayuda, ya que no quiero exponer estos métodos de ayuda en la interfaz que implementa el servicio porque eso los obliga a ser públicos. Esto puede crear algunos problemas de prueba / burla de unidad.  
  • Peor aún, cualquier sincronización que quisiera hacer se filtra a través de algún método de conveniencia externa (por ejemplo, lockDownAssets () durante una actualización de encuesta). Antes, las clases internas tenían acceso a bloqueos privados.

    En resumen, dividir las clases pierde parte de la encapsulación que me gusta. Pero dejarlos adentro puede llevar a algunos archivos java grandes. Todavía tengo que encontrar una buena manera de lidiar con esto. C ++ tenía el concepto de " amigos " que rara vez me he perdido, pero en realidad ayudaría en este caso.

    ¿Pensamientos?

        
  • ¿Fue útil?

    Solución

    En las clases internas a nivel de bytecode son solo clases de Java. Dado que el verificador de bytecode de Java no permite el acceso a miembros privados, genera métodos de acceso sintéticos para cada campo privado que utilice. Además, para vincular la clase interna con su instancia adjunta, el compilador agrega un puntero sintético al 'este' externo.

    Considerando esto, las clases internas son solo una capa de sintaxis de azúcar. Son convenientes y usted ha enumerado algunos puntos positivos, así que enumeraré algunos aspectos negativos que podría considerar:

    • Su clase interna tiene una dependencia oculta a la clase principal entera , que confunde su interfaz de entrada. Si lo extrae como paquete privado, tendrá la oportunidad de mejorar su diseño y hacerlo más fácil de mantener. Inicialmente es más detallado, pero a menudo se encuentra que:
      • En lugar de exponer 10 accesores, realmente desea compartir un objeto de valor único. A menudo, encontrará que realmente no necesita una referencia a toda la clase externa. Esto también funciona bien con IoC.
      • En lugar de proporcionar métodos para el bloqueo explícito, es más fácil encapsular la operación con su contexto en una clase separada (o moverla a una de las dos clases: externa o anteriormente interna).
      • Los métodos de conveniencia pertenecen a las clases de utilidad privada del paquete. Puede usar la importación estática Java5 para que aparezcan como locales.
    • Su clase externa puede pasar por alto cualquier nivel de protección y acceder directamente a los miembros privados de su clase interna. Esto no es malo en sí mismo, pero le quita uno de los medios de lenguaje para expresar su diseño.
    • Como su clase interna está incrustada en exactamente una clase externa, la única forma de reutilizarla es subclase de la clase externa. Una alternativa sería pasar una referencia explícita a una interfaz de paquete privado que implementa la clase externa. Esto le permitiría burlarse de lo externo y probar mejor a la clase interna.
    • Aunque los depuradores recientes son bastante buenos, he experimentado problemas con la depuración de clases internas antes (confusión condicional del punto de interrupción, no detenerme en los puntos de interrupción, etc.)
    • Las clases privadas inflan su código de bytes. Vea mi primer párrafo: a menudo hay una API que podría usar y reducir el número de cruces sintéticos.

    P.S. Estoy hablando de clases internas no triviales (especialmente las que no implementan ninguna interfaz). Las implementaciones de escucha de tres líneas son buenas.

    Otros consejos

    No olvides considerar por qué estás tratando de dividir tu gran clase. ¿Es para propósitos de ingeniería de software? P.ej. es un hotspot de programación y tiene un archivo tan grande que causa complicadas combinaciones en su equipo de desarrolladores.

    ¿Es solo un deseo general de evitar las clases grandes? En cuyo caso es posible que se dedique más tiempo a mejorar el código que tiene.

    ¿Es el código cada vez más difícil de administrar, por ejemplo, La depuración y la garantía de evitar los efectos secundarios no deseados son cada vez más difíciles.

    El comentario de Rick sobre el uso de pruebas unitarias para garantizar un comportamiento constante y consistente es muy valioso y una buena idea. Es posible que el diseño actual simplemente excluya la refactorización y que sea mejor que vuelva a implementar el mismo comportamiento a partir de la interfaz del original. ¡Prepárate para un montón de pruebas de regresión!

    La línea entre la encapsulación y la separación puede ser difícil de caminar. Sin embargo, creo que el problema principal aquí es que necesitas algún tipo de modelo de interacción sólido para usar como base para separar tus clases.

    Creo que es razonable tener clases de utilidad de ayudante externo que se usan en muchos lugares, siempre que no tengan un efecto secundario, no veo un problema. También es razonable tener clases estáticas de ayuda, siempre que estén bien organizadas, que contengan métodos de uso frecuente, como checkAssetIsValid (). Esto es asumiendo que checkAssetIsValid no necesita acceder a ningún estado externo que no sea el objeto que le está pasando.

    Lo más importante para la separación es no tener objetos que compartan referencias permanentes en muchas de estas clases. Me gusta mirar a la programación funcional para orientación. Cada clase no debe llegar a las entrañas de otras clases y al estado cambiante. En su lugar, cada clase que funciona debería producir y consumir objetos de contenedor.

    La visualización puede ser muy útil también. Noté un hilo en el tema de las herramientas de visualización de Java aquí . Idealmente, su diagrama de interacción de clase debería parecerse más a un árbol que a un gráfico.

    También, solo quiero señalar que refactorizar una clase grande en clases más pequeñas puede ser extremadamente difícil. Es mejor construir un conjunto de pruebas unitarias para la interfaz pública, por lo menos para que sea inmediatamente obvio si rompes algo. Sé que las pruebas me han ahorrado innumerables horas en el pasado.

    Esperemos que algo de esto sea útil. Estoy divagando aquí.

    No soy un fanático del uso excesivo de clases internas. Creo que realmente no ofrecen ninguna ventaja (cuando se usa en el extremo) de que poner el código en una clase normal no lo haría, y solo sirven para hacer que el archivo de la clase sea innecesariamente grande y difícil de seguir.

    ¿Cuál es el daño si tiene que aumentar la visibilidad de algunos métodos? ¿Va a romper completamente tu abstracción o interfaz? Pienso que, con demasiada frecuencia, los programadores tienden a hacer que todo sea privado por defecto, donde realmente no hay nada de malo en dejar que otras clases llamen a sus métodos, si su diseño está realmente basado en OO, es decir.

    Si todas tus " clases de ayuda interna " Si necesita acceder a algunos de los mismos métodos, considere colocarlos en una clase base para que puedan compartirse a través de la herencia.

    Yeap. Probablemente necesite refactorizar a esos ayudantes y no moverlos a todos como son. Algunas cosas pertenecen al servicio, otras al ayudante. Se deben usar nuevas clases probables para encapsular los datos.

    Una de las posibilidades que puede usar es AOP para proporcionar acceso de grano fino e incluir en el corte de puntos que el método solo debe invocarse desde el " amigo " clase. Aún su método estaría expuesto :(

    Supongo que no hay una solución fácil para esto.

    Licenciado bajo: CC-BY-SA con atribución
    No afiliado a StackOverflow
    scroll top