Ответ 1
Возможно, вы могли бы использовать Aspect Oriented Programming для захвата вызовов этой функции и вместо этого вернете свою собственную версию?
Spring предлагает некоторые функции AOP, но есть и другие библиотеки, которые также делают это.
Я не хочу обсуждать достоинства этого подхода, просто, если это возможно. Я считаю, что ответ "нет". Но может быть, кто-то удивит меня!
Представьте, что у вас есть класс виджетов. Он имеет метод calculateHeight()
, который возвращает высоту. Высота слишком велика - этот результат в кнопках (скажем), которые слишком велики. Вы можете расширить DefaultWidget, чтобы создать свой собственный NiceWidget, и реализовать свой собственный calculateHeight()
, чтобы вернуть более удобный размер.
Теперь библиотечный класс WindowDisplayFactory создает экземпляр DefaultWidget довольно сложным способом. Вы хотите, чтобы он использовал ваш NiceWidget. Метод класса factory выглядит примерно так:
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;
}
Это сделка. Результат имеет DefaultWidget глубоко внутри иерархии других объектов. Вопрос: как получить этот метод factory для использования моего собственного NiceWidget? Или, по крайней мере, получить там свой собственный calculateHeight()
. В идеале, я бы хотел, чтобы обезьяна заплатила DefaultWidget так, чтобы его расчетHeight сделал правильную вещь...
public class MyWindowDisplayFactory {
public IWidget createView(Component parent) {
DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
return super.createView(parent);
}
}
Что я могу сделать в Python, Ruby и т.д. Однако я придумал имя setMethod()
. Остальные варианты доступны для меня:
createView()
в мой собственный класс, который наследуется от класса factoryКласс factory не может быть изменен - он является частью API основной платформы. Я попытался отразить полученный результат, чтобы добраться до виджета, который (в конце концов) был добавлен, но это несколько слоев виджета, и где-то он привыкает к инициализации других вещей, вызывая нечетные побочные эффекты.
Любые идеи? Мое решение до сих пор заключается в задании копирования-вставки, но в том, что компьютер, который требует отслеживания изменений родительского класса factory при обновлении до более новых версий платформы, и мне было бы интересно услышать другие варианты.
Возможно, вы могли бы использовать Aspect Oriented Programming для захвата вызовов этой функции и вместо этого вернете свою собственную версию?
Spring предлагает некоторые функции AOP, но есть и другие библиотеки, которые также делают это.
Одним уродливым решением было бы поместить вашу собственную реализацию DefaultWidget (с тем же FQCN) ранее на пути класса, чем обычная реализация. Это ужасный взлом, но любой другой подход, о котором я могу думать, еще хуже.
Только моя концепция,
Возможно, что AOP с использованием метода байт-кода может ввести аспект метода calculateHeight.
Затем вы можете включить исправление переменной ThreadLocal или else.
cglib - это библиотека Java, которая может делать некоторые вещи, похожие на переключение на обезьяны - она может манипулировать байт-кодом во время выполнения, чтобы изменить определенное поведение, Я не уверен, что он может сделать именно то, что вам нужно, но это стоит посмотреть...
Возможно, вы могли бы написать собственный загрузчик классов.
http://en.wikipedia.org/wiki/Classloader
http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html
Вполне возможно, что monkeypatch в Java, используя Unsafe.putObject и finder класса. Написал здесь сообщение в блоге:
https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/
Объектно-ориентированный способ сделать это - создать оболочку, реализующую IWidget, делегируя все вызовы фактическому виджету, кроме calculateHeight, что-то вроде:
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);
}
}
Чтобы это сработало, вам нужно перехватить все творения виджета, который вы хотите заменить, что, вероятно, означает создание аналогичной обертки для WidgetFactory. И вы должны быть в состоянии настроить, какой WidgetFactory использовать.
Это также зависит от того, что клиент не пытается вернуть IWidget в DefaultWidget...
Только предложения, о которых я могу думать:
Пройдите через API-интерфейс библиотеки, чтобы увидеть, есть ли способ переопределить значения по умолчанию и размер. Калибровка может запутаться в качелях (по крайней мере для меня), setMinimum, setMaximum, setdefault, setDefaultOnThursday,.... Это возможно. Если вы можете связаться с дизайнером библиотеки, вы можете найти ответ, который облегчит необходимость неприятного взлома.
Возможно, расширение factory только переопределяет некоторый параметр размера по умолчанию? зависит от factory, но это возможно.
Создание класса с тем же именем может быть единственным другим вариантом, так как другие указали на него уродливо, и вы можете его забыть и сломать материал при обновлении библиотеки api или развертывании в другой среде и забыть, почему вы создали путь к классам.
Вы можете попробовать использовать такие инструменты, как PowerMock/Mockito. Если вы можете издеваться над тестами, вы можете издеваться над производством.
Однако эти инструменты не предназначены для использования таким образом, поэтому вам придется самостоятельно подготовить среду и не сможете использовать JUnit-бегуны, как в тестах...
Ну, я продолжаю пытаться опубликовать предложения, и тогда я вижу, что они не будут работать или что вы уже упомянули, что вы их пробовали.
Лучшее решение, о котором я могу думать, - это подкласс WindowDisplayFactory, затем в подклассе createView(), сначала вызовите super.createView(), затем измените возвращаемый объект, чтобы полностью выкинуть виджет и заменить его экземпляром подкласс, который делает то, что вы хотите. Но виджет используется для инициализации материала, поэтому вам придется изменить все эти параметры.
Затем я думаю об использовании отражения возвращаемого объекта из createView() и пытается исправить ситуацию таким образом, но опять же, что волосатый, потому что так много вещей было инициализировано виджетами. Я думаю, что я попытаюсь использовать этот подход, хотя это было бы достаточно просто, чтобы оправдать его при копировании и вставке.
Я буду смотреть это, а также думать, могу ли я придумать какие-либо другие идеи. Очевидно, что Java Reflection хорош, но он не может победить динамическую интроспекцию, которую я видел, доступную на таких языках, как Perl и Python.