2008-12-19 6 views
19

Ich möchte nicht die Vorzüge dieses Ansatzes diskutieren, nur wenn es möglich ist. Ich glaube, die Antwort ist "Nein". Aber vielleicht wird mich jemand überraschen!Ist es möglich in Java Affen zu patchen?

Stellen Sie sich vor, Sie haben eine Kern-Widget-Klasse. Es hat eine Methode calculateHeight(), die eine Höhe zurückgibt. Die Höhe ist zu groß - dies führt zu Knöpfen, die zu groß sind. Sie können DefaultWidget erweitern, um ein eigenes NiceWidget zu erstellen und Ihre eigene calculateHeight() zu implementieren, um eine bessere Größe zu erhalten.

Eine Bibliotheksklasse WindowDisplayFactory instanziiert DefaultWidget in einer ziemlich komplexen Methode. Sie möchten, dass Ihr NiceWidget verwendet wird. Die Methode der Fabrikklasse sieht in etwa so aus:

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; 
} 

Das ist der Deal. Das Ergebnis hat das DefaultWidget tief in einer Hierarchie anderer Objekte. Die Frage - Wie bekomme ich diese Factory-Methode, um mein eigenes NiceWidget zu verwenden? Oder zumindest meinen eigenen calculateHeight() da drin. Im Idealfall würde Ich mag an Affen zu können DefaultWidget Patch, so dass seine calculateHeight das Richtige getan haben ...

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

Was ist das, was ich in Python tun könnte, Ruby, etc. Ich erfunden habe die Name setMethod() obwohl. Die anderen Optionen offen sind für mich:

  • Kopieren und den Code der createView() Methode in meine eigene Klasse einfügen, die von der Factory-Klasse erbt mit Widgets
  • Leben, die zu groß sind

Die Die Factory-Klasse kann nicht geändert werden - sie ist Teil einer Core-Plattform-API. Ich habe versucht, über das zurückgegebene Ergebnis nachzudenken, um zu dem Widget zu kommen, das (irgendwann) hinzugefügt wurde, aber es sind mehrere Widget-Ebenen unten und irgendwo wird es verwendet, um andere Sachen zu initialisieren, was zu seltsamen Nebenwirkungen führt.

Irgendwelche Ideen? Meine Lösung ist bisher der Copy-Paste-Job, aber das ist ein Cop-Out, bei dem die Änderungen in der übergeordneten Factory-Klasse beim Upgrade auf neuere Versionen der Plattform protokolliert werden müssen, und ich wäre an weiteren Optionen interessiert.

Antwort

9

Vielleicht könnten Sie Aspect Oriented Programming verwenden, um Aufrufe an diese Funktion aufzufangen und stattdessen Ihre eigene Version zurückzugeben?

Frühling bietet einige AOP-Funktionalität, aber es gibt andere Bibliotheken, die es auch tun.

+0

Klingt gut und möglicherweise die nächste echte Lösung. Vielleicht ein bisschen Overkill, um eine Spring-Abhängigkeit hinzuzufügen, obwohl für die App nur für diese Korrektur keine existiert. Ein benutzerdefinierter Klassenlader funktioniert möglicherweise nicht gut mit Eclipse RCP, den ich auch verwende ... – richq

+2

Sie brauchen Spring nicht, um AOP zu machen, nur AspectJ. Es gibt auch ein Eclipse-Plugin: http://www.eclipse.org/aspectj/ –

6

Eine hässliche Lösung wäre, Ihre eigene Implementierung von DefaultWidget (mit demselben FQCN) früher auf dem Classpath als die normale Implementierung zu setzen. Es ist ein schrecklicher Hack, aber jeder andere Ansatz, den ich mir vorstellen kann, ist noch schlimmer.

+0

Leider würde ich mit keiner Weise 99% des * real * DefaultWidget verwenden will, am Ende, um darauf zuzugreifen. – richq

0

Die objektorientierte Art und Weise, dies zu tun wäre, einen Wrapper zu erstellen IWidget Implementierung, alle Anrufe an die tatsächliche Widget delegieren, außer calculateHeight, so etwas wie:

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); 
    } 
} 

Damit dies funktioniert, müssen Sie Abfangen aller Kreationen des Widgets, das Sie ersetzen möchten, was wahrscheinlich bedeutet, dass Sie einen ähnlichen Wrapper für die WidgetFactory erstellen. Und Sie müssen in der Lage sein zu konfigurieren, welche WidgetFactory verwendet werden soll.

Es hängt auch von keinem Client versucht, die IWidget zurück zu DefaultWidget zu werfen ...

+0

Dies ist das Staty Design-Muster und ein klassisches Java-Tool. Nichts mit Affenpflaster zu tun. Sie können Affe Patch oder nicht mögen, aber es ist ein Anwser zu einem ganz besonderen Problem. –

+0

Absolut, ich schreibe dies nur als eine mögliche Option. Es gibt viele Alternativen hier, die am besten geeignet ist, hängt vom tatsächlichen Szenario ab: die Erweiterungsmöglichkeiten der API in Frage, Flexibilität, Plattform, muss vorwärts/rückwärts kompatibel etc. –

0

Nur Vorschläge, die ich denken kann:

  1. Dig durch die Bibliothek API, um zu sehen, ob es eine Möglichkeit gibt, Überschreiben der Standardwerte und der Größe.Sizing kann im Swing (zumindest für mich) verwirrend sein, setMinimum, setMaximum, setdefault, setDefaultOnThursday, .... Es ist möglich, dass es einen Weg gibt. Wenn Sie Kontakt mit dem/den Bibliotheksdesigner/n aufnehmen können, finden Sie möglicherweise eine Antwort, die die Notwendigkeit eines unangenehmen Hackens verringert.

  2. Vielleicht die Fabrik erweitern nur einige Standard-Dimensionierung Parameter überschreiben? hängt von der Fabrik ab, aber es könnte möglich sein.

eine Klasse mit dem gleichen Namen anlegen könnte die einzige andere Option sein, wie andere darauf hingewiesen haben, es ist hässlich, und Sie sind verpflichtet, es zu vergessen und so zu brechen, wenn Sie die api Bibliothek aktualisieren oder in einem anderen bereitstellen Umgebung und vergiss, warum du den Klassenpfad so eingerichtet hast.

+0

Ich suche weiter nach der "setMinimumNoReallyIMeanItStopIgnoringMe" -Methode. –

+0

Heh - es ist eigentlich viel schlimmer als das - der Code in Frage ist nicht einmal Swing. Wenn man sich die API ansieht, bittet sie um die Abhängigkeitsinjektion, aber ich kann sehen, warum sie diese nicht hat (um die Dinge einfach zu halten). Hör auf! – richq

3

Nur mein Konzept Idee,

Es ist möglich, dass AOP verwenden, mit Bytecode-Engineering-Weg, einen Aspekt der calculateHeight Methode zu injizieren.

Dann können Sie Sie Patch von ThreadLocal oder auch Variable aktivieren.

-1

Nun, ich versuche immer, Vorschläge zu posten, und dann sehe ich, dass sie nicht funktionieren oder dass Sie bereits erwähnt haben, dass Sie sie ausprobiert haben.

Die beste Lösung, die ich mir vorstellen kann, ist die Unterklasse WindowDisplayFactory, dann in der Methode createView() der Unterklasse zuerst super.createView() aufrufen, dann das zurückgegebene Objekt ändern, um das Widget vollständig zu löschen und durch eine Instanz zu ersetzen der Unterklasse, die das tut, was Sie wollen. Aber das Widget wird verwendet, um Dinge zu initialisieren, also müsstest du all diese ändern.

Dann denke ich an Reflexion über das zurückgegebene Objekt von createView() und versuchen, die Dinge auf diese Weise zu beheben, aber wieder, das ist haarig, weil so viel Zeug mit dem Widget initialisiert wurde. Ich denke, ich würde versuchen, diese Herangehensweise zu verwenden, wenn es einfach genug wäre, es gegenüber dem Kopieren und Einfügen zu rechtfertigen.

Ich werde das zusehen und darüber nachdenken, ob ich mir weitere Ideen einfallen lassen kann. Java Reflection ist sicher schön, aber es kann die dynamische Introspektion, die ich in Sprachen wie Perl und Python gesehen habe, nicht übertreffen.

+0

Ja, es gibt eine Menge Zeug in den extra neuen Klassen in der createView - am Ende würden die Reflection + Fix Ups wahrscheinlich waren zerbrechlicher (schwieriger mit neuen Versionen der WidgetFactory zu pflegen) als nur Kopieren-Einfügen. – richq

2

cglib ist eine Java-Bibliothek, die einige Dinge ähnlich wie Monkey-Patching tun kann - sie kann Bytecode zur Laufzeit ändern, um bestimmte Verhaltensweisen zu ändern. Ich bin mir nicht sicher, ob es genau das tut, was Sie brauchen, aber es ist einen Blick wert ...

+1

Andere Tools zum Manipulieren von Byte-Code: http://jakarta.apache.org/bcel/ http://asm.objectweb.org/ –

0

können Sie versuchen, Tools wie PowerMock/Mockito verwenden. Wenn Sie in Tests spotten können, können Sie auch in der Produktion spotten.

Allerdings sind diese Tools nicht so konzipiert, dass sie auf diese Weise verwendet werden. Sie müssen also die Umgebung selbst vorbereiten und können die JUnit-Runner nicht wie in Tests verwenden ...