2008-12-19 14 views
19

No quiero discutir los méritos de este enfoque, solo si es posible. Creo que la respuesta es "no". ¡Pero tal vez alguien me sorprenda!¿Es posible el parche de mono en Java?

Imagine que tiene una clase de widget central. Tiene un método calculateHeight(), que devuelve una altura. La altura es demasiado grande; esto da como resultado botones (por ejemplo) demasiado grandes. Puede extender DefaultWidget para crear su propio NiceWidget e implementar su propio calculateHeight() para obtener un tamaño mejor.

Ahora una clase de biblioteca WindowDisplayFactory, instancia el DefaultWidget en un método bastante complejo. Le gustaría usar su NiceWidget. El método de la clase de fábrica se ve más o menos así:

public IWidget createView(Component parent) { 
    DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY); 

    // bunch of ifs ... 
    SomeOtherWidget bla = new SomeOtherWidget(widget); 
    SomeResultWidget result = new SomeResultWidget(parent); 
    SomeListener listener = new SomeListener(parent, widget, flags); 

    // more widget creation and voodoo here 

    return result; 
} 

Ese es el trato. El resultado tiene el widget predeterminado en una jerarquía de otros objetos. La pregunta: ¿cómo obtener este método de fábrica para usar mi propio NiceWidget? O al menos obtener mi propio calculateHeight() allí. Idealmente, me gustaría ser capaz de parchear el mono DefaultWidget de manera que su calculateHeight hizo lo correcto ...

public class MyWindowDisplayFactory { 
    public IWidget createView(Component parent) { 
     DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight); 
     return super.createView(parent); 
    } 
} 

Que es lo que podía hacer en Python, Ruby, etc. He inventado el nombre setMethod() sin embargo. Las otras opciones abiertas para mí son:

  • Copiar y pegar el código del método createView() en mi propia clase que hereda de la clase de fábrica
  • Living con widgets que son demasiado grandes

El la clase de fábrica no se puede cambiar; es parte de una API de la plataforma central. Intenté reflexionar sobre el resultado obtenido para acceder al widget que (finalmente) se agregó, pero se trata de varias capas de widgets y en algún lugar se usa para inicializar otras cosas, lo que causa extraños efectos secundarios.

¿Alguna idea? Mi solución hasta ahora es el trabajo de copiar y pegar, pero eso es una salida de policía que requiere el seguimiento de los cambios en la clase principal de fábrica cuando se actualiza a versiones más nuevas de la plataforma, y ​​me gustaría escuchar otras opciones.

Respuesta

9

¿Quizás podría usar la Programación Orientada a Aspectos para atrapar llamadas a esa función y devolver su propia versión?

Spring ofrece algunas funcionalidades AOP, pero hay otras bibliotecas que también lo hacen.

+0

Suena bien y posiblemente sea la solución real más cercana. Tal vez un poco exagerado para agregar una dependencia de Spring donde no existía ninguno para la aplicación solo para esta reparación. Es posible que un cargador de clases personalizado no funcione bien con Eclipse RCP, que también utilizo ... – richq

+2

No necesita Spring para hacer AOP, simplemente obtenga AspectJ. También hay un plugin eclipse: http://www.eclipse.org/aspectj/ –

6

Una solución fea sería poner su propia implementación de DefaultWidget (con el mismo FQCN) anteriormente en el Classpath que la implementación normal. Es un hack terrible, pero cada otro enfoque que puedo pensar es aún peor.

+0

desgracia que iba a terminar queriendo utilizar el 99% de la * real * DefaultWidget y no hay forma de acceder a ella. – richq

0

La forma orientada a objetos de hacer esto sería la creación de un envoltorio de implementar iwidget, delegando todas las llamadas al widget real, excepto calculateHeight, algo así como:

class MyWidget implements IWidget { 
    private IWidget delegate; 
    public MyWidget(IWidget d) { 
     this.delegate = d; 
    } 
    public int calculateHeight() { 
     // my implementation of calculate height 
    } 
    // for all other methods: { 
    public Object foo(Object bar) { 
     return delegate.foo(bar); 
    } 
} 

Para que esto funcione, es necesario intercepte todas las creaciones del widget que desea reemplazar, lo que probablemente signifique crear un contenedor similar para WidgetFactory. Y debe poder configurar qué WidgetFactory usar.

También depende de ningún cliente tratando de emitir el iwidget de nuevo a DefaultWidget ...

+0

Este es el patrón de diseño de estrategias y una herramienta clásica de Java. Nada que ver con el parche de mono. Te puede gustar el parche de mono o no, pero es un indicador de un problema muy especial. –

+0

Absolutamente, solo estoy publicando esto como una posible opción. Aquí hay muchas alternativas, que se adaptan mejor depende del escenario real: las posibilidades de extensión de la API en cuestión, flexibilidad, plataforma, necesidad de ser compatible con versiones anteriores/posteriores, etc. –

0

sólo sugerencias que se me ocurren:

  1. excavar a través de la API de biblioteca para ver si hay alguna manera de anular los valores predeterminados y el tamaño.El tamaño puede ser confuso en el swing (al menos para mí), setMinimum, setMaximum, setdefault, setDefaultOnThursday, .... Es posible que haya una manera. Si puede ponerse en contacto con el/los diseñador (es) de la biblioteca, puede encontrar una respuesta que alivie la necesidad de piratería desagradable.

  2. ¿Quizás extienda la fábrica solo anulando algunos parámetros de tamaño predeterminados? depende de la fábrica, pero podría ser posible.

Crear una clase con el mismo nombre que podría ser la única otra opción, como otros han señalado que es feo y que está obligado a olvidarlo y romper cosas cuando se actualiza la biblioteca API o implementar de una manera diferente ambiente y olvide por qué tenía el classpath configurado de esa manera.

+0

Sigo buscando el método "setMinimumNoReallyIMeanItStopIgnoringMe". –

+0

Heh - en realidad es mucho peor que esto - el código en cuestión ni siquiera es Swing. En cuanto a la API, pide inyección de dependencia, pero puedo ver por qué no tiene esto (para mantener las cosas simples). Ho hum! – richq

3

Sólo mi concepto de la idea,

Es posible que el uso de AOP, la forma en la ingeniería de código de bytes, para inyectar un aspecto al método calculateHeight.

Luego, puede habilitar el parche por ThreadLocal o la variable.

-1

Bueno, sigo tratando de publicar sugerencias, y luego veo que no funcionarán o que ya has mencionado que las has probado.

La mejor solución que puedo pensar es subclase WindowDisplayFactory, luego en el método createView() de la subclase, primero llame a super.createView(), luego modifique el objeto devuelto para descartar completamente el widget y reemplazarlo por una instancia de la subclase que hace lo que quieres. Pero el widget se usa para inicializar cosas, por lo que tendrías que cambiarlas todas.

Luego pienso en usar la reflexión sobre el objeto devuelto desde createView() y tratando de arreglar las cosas de esa manera, pero de nuevo, eso es peludo porque se inicializaron muchas cosas con el widget. Creo que trataría de usar ese enfoque, si fuera lo suficientemente simple como para justificarlo sobre copiar y pegar.

Voy a estar viendo esto, además de pensar en ver si puedo encontrar otras ideas. La Reflexión de Java es agradable, pero no puede superar la introspección dinámica que he visto disponible en idiomas como Perl y Python.

+0

Sí, hay una gran cantidad de cosas hechas en las clases nuevas en createView - al final la reflexión + fix ups probablemente han sido más frágiles (más difíciles de mantener con las nuevas versiones de WidgetFactory) que simplemente copiar y pegar. – richq

2

cglib es una biblioteca de Java que puede hacer algunas cosas similares a parche de mono - puede manipular bytecode en tiempo de ejecución para cambiar ciertos comportamientos. No estoy seguro si puede hacer exactamente lo que necesita, pero vale la pena verlo ...

+1

Otras herramientas para manipular byte-code: http://jakarta.apache.org/bcel/ http://asm.objectweb.org/ –

0

Puedes probar a utilizar herramientas como PowerMock/Mockito. Si puede simular pruebas, también puede simular la producción.

Sin embargo, estas herramientas no están diseñadas para ser utilizadas de esta forma, por lo que tendrá que preparar el entorno usted mismo y no podrá usar los corredores JUnit como lo hace en las pruebas ...

Cuestiones relacionadas