Я не хочу обсуждать достоинства этого подхода, просто если это возможно. Я считаю, что ответ - «нет». Но может кто меня удивит!
Представьте, что у вас есть базовый класс виджетов. У него есть метод calculateHeight(), который возвращает высоту. Высота слишком велика - в результате кнопки (скажем) становятся слишком большими. Вы можете расширить DefaultWidget, чтобы создать свой собственный NiceWidget, и реализовать свой собственный calculateHeight(), чтобы вернуть более удобный размер.
Теперь класс библиотеки WindowDisplayFactory создает экземпляр DefaultWidget довольно сложным методом. Вы хотите, чтобы он использовал ваш NiceWidget. Метод фабричного класса выглядит примерно так:
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 находится глубоко внутри иерархии других объектов. Вопрос - как заставить этот заводской метод использовать мой собственный NiceWidget? Или, по крайней мере, поставьте туда мой собственный calculateHeight(). В идеале я хотел бы иметь возможность обезьяны исправлять DefaultWidget, чтобы его calculateHeight работал правильно ...
public class MyWindowDisplayFactory {
public IWidget createView(Component parent) {
DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
return super.createView(parent);
}
}
Это то, что я мог сделать в Python, Ruby и т. д. Я придумал имя setMethod(). Мне доступны следующие другие варианты:
createView() в мой собственный класс, наследуемый от фабричного классаКласс фабрики не может быть изменен - он является частью API базовой платформы. Я попытался отразить возвращенный результат, чтобы добраться до виджета, который (в конце концов) был добавлен, но он находится на нескольких уровнях виджетов и где-то он используется для инициализации других вещей, вызывая странные побочные эффекты.
Любые идеи? Моим решением пока является копирование и вставка, но это отстой, требующий отслеживания изменений в родительском фабричном классе при обновлении до более новых версий платформы, и мне было бы интересно услышать другие варианты.




Одно уродливое решение - разместить собственную реализацию DefaultWidget (с тем же FQCN) раньше в пути к классам, чем обычная реализация. Это ужасный взлом, но все остальные подходы, которые я могу придумать, еще хуже.
Объектно-ориентированный способ сделать это - создать оболочку, реализующую 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 ...
Это паттерн статического дизайна и классический инструмент Java. Ничего общего с патчем обезьяны. Вы можете любить патч обезьяны или нет, но это ответ на совершенно особую проблему.
Безусловно, я просто публикую это как возможный вариант. Здесь есть множество альтернатив, которые лучше всего подходят в зависимости от фактического сценария: возможности расширения рассматриваемого API, гибкость, платформа, необходимость быть прямой / обратной совместимостью и т. д.
Только предложения, о которых я могу думать:
Изучите API библиотеки, чтобы увидеть, есть ли способ переопределить значения по умолчанию и размер. Размер может сбивать с толку (по крайней мере, для меня), setMinimum, setMaximum, setdefault, setDefaultOnThursday, .... Возможно, есть способ. Если вы сможете связаться с дизайнером (-ами) библиотеки, вы можете найти ответ, который избавит вас от неприятного взлома.
Возможно, расширить фабрику, заменив только некоторые параметры размера по умолчанию? зависит от завода, но это возможно.
Создание класса с тем же именем может быть единственным другим вариантом, поскольку другие указали, что это уродливо, и вы можете забыть об этом и сломать материал, когда обновляете библиотеку api или развертываете в другой среде и забываете, почему у вас был classpath настроен таким образом.
Я продолжаю искать метод "setMinimumNoReallyIMeanItStopIgnoringMe".
Хех - на самом деле это намного хуже - рассматриваемый код даже не Swing. Глядя на API, он требует внедрения зависимостей, но я понимаю, почему у него этого нет (для простоты). Хо хм!
Возможно, вы могли бы использовать аспектно-ориентированное программирование, чтобы перехватывать вызовы этой функции и вместо этого возвращать вашу собственную версию?
Spring предлагает некоторую функциональность АОП, но есть и другие библиотеки, которые это делают.
Звучит хорошо и, возможно, самое близкое реальное решение. Возможно, будет немного лишним добавить в приложение зависимость Spring, которой не было, только для этого исправления. Пользовательский загрузчик классов может плохо работать с Eclipse RCP, который я тоже использую ...
Вам не нужен Spring для АОП, просто скачайте AspectJ. Также есть плагин eclipse: eclipse.org/aspectj
Просто моя концептуальная идея,
Возможно использование АОП с использованием метода байт-кода для добавления аспекта в метод calculateHeight.
Затем вы можете включить патч с помощью ThreadLocal или другой переменной.
Что ж, я продолжаю пытаться публиковать предложения, а потом вижу, что они не работают или что вы уже упоминали, что пробовали их.
Лучшее решение, которое я могу придумать, - создать подкласс WindowDisplayFactory, затем в методе подкласса createView () сначала вызвать super.createView (), затем изменить возвращенный объект, чтобы полностью выбросить виджет и заменить его экземпляром подкласса, который делает что хочешь. Но виджет используется для инициализации, поэтому вам придется все это изменить.
Затем я думаю об использовании отражения на возвращаемом объекте из createView () и попытках исправить ситуацию таким образом, но опять же, это непросто, потому что так много всего было инициализировано с помощью виджета. Я думаю, что я бы попытался использовать этот подход, если бы он был достаточно простым, чтобы оправдать его перед копированием и вставкой.
Я буду смотреть это, а также буду думать, смогу ли я придумать какие-нибудь другие идеи. Java Reflection, конечно, хорош, но он не может превзойти динамический самоанализ, который я видел в таких языках, как Perl и Python.
Да, в дополнительных классах new'd в createView сделано изрядное количество вещей - в конце концов, отражение + исправления, вероятно, были бы более хрупкими (труднее поддерживать с новыми версиями WidgetFactory), чем просто копирование и вставка .
cglib - это библиотека Java, которая может делать некоторые вещи, похожие на исправление обезьян - она может манипулировать байт-кодом во время выполнения для изменения определенного поведения. Я не уверен, что он может сделать именно то, что вам нужно, но на него стоит взглянуть ...
Другие инструменты для управления байтовым кодом: jakarta.apache.org/bcelasm.objectweb.org
Вы можете попробовать использовать такие инструменты, как PowerMock / Mockito. Если вы можете издеваться в тестах, вы также можете имитировать в продакшене.
Однако эти инструменты на самом деле не предназначены для использования таким образом, поэтому вам придется самостоятельно подготовить среду, и вы не сможете использовать бегуны JUnit, как вы это делаете в тестах ...
Вполне возможно выполнить monkeypatch на Java, используя Unsafe.putObject и средство поиска классов. Написал здесь сообщение в блоге:
https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/
Это не совсем обезьяна. Это (просто) изменение значения поля путем взлома абстракции. Теперь, если вы добавляли поля / добавляли методы / изменяли методы во время выполнения (не время загрузки) ... это обезьяна исправления.
Из Википедии: «Патч обезьяны - это способ программы локально расширить или изменить поддерживающее системное программное обеспечение (затрагивая только работающий экземпляр программы)». JVM запускается при изменении методов, так что это обезьяна.
Согласно вашей интерпретации этого определения, выполнение любого оператора присваивания также будет обезьяньим исправлением. А также "хитрые" вещи, такие как изменение неизменяемой строки. В любом случае, большинство программистов на Java не согласятся с вашей интерпретацией. Вот почему все остальные предложения относятся к таким вещам, как AOP, разработка байт-кода и фиктивные фреймворки.
> А также "хитрые" вещи, такие как изменение неизменяемой строки. Да, по определению. Из вашего связанного URL: «Нет, это не похоже ни на что из этого. Это просто динамическая замена атрибутов во время выполнения». Атрибуты не ограничиваются методами. Такие вещи, как tersesystems.com/blog/2016/01/19/…, конечно же, тоже обезьяны.
К сожалению, я бы хотел использовать 99% виджета настоящий DefaultWidget без возможности доступа к нему.