22

如果可能的话,我不想讨论这种方法的优点。我相信答案是“不”。但也许有人会让我大吃一惊!

想象一下,您有一个核心小部件类。它有一个方法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 的一部分。我尝试对返回的结果进行反射以获取(最终)添加的小部件,但它向下有几个小部件层,并且在某个地方它被用来初始化其他东西,导致奇怪的副作用。

有任何想法吗?到目前为止,我的解决方案是复制粘贴工作,但这是一个需要在升级到更新版本的平台时跟踪父工厂类中的更改的警察,我很想听听其他选项。

4

9 回答 9

9

也许您可以使用面向方面的编程来捕获对该函数的调用并返回您自己的版本?

Spring 提供了一些 AOP 功能,但也有其他库也可以做到这一点。

于 2008-12-19T15:26:09.267 回答
7

一种丑陋的解决方案是将您自己的 DefaultWidget 实现(具有相同的 FQCN)放在 Classpath 上,而不是正常实现。这是一个可怕的黑客攻击,但我能想到的所有其他方法都更糟糕。

于 2008-12-19T15:14:46.030 回答
3

只是我的概念想法,

可以使用 AOP,通过字节码工程的方式,向 calculateHeight 方法注入一个切面。

然后,您可以通过 ThreadLocal 或其他变量启用您的补丁。

于 2008-12-19T15:28:12.647 回答
2

cglib是一个 Java 库,可以做一些类似于猴子补丁的事情——它可以在运行时操纵字节码来改变某些行为。我不确定它是否可以完全满足您的需求,但值得一看...

于 2008-12-19T17:00:04.710 回答
2

使用 Unsafe.putObject 和类查找器在 Java 中进行猴子补丁是完全可能的。在这里写了一篇博文:

https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/

于 2017-05-01T18:56:31.720 回答
0

这样做的面向对象的方法是创建一个实现 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 ...

于 2008-12-19T15:21:18.510 回答
0

只有我能想到的建议:

  1. 挖掘库 API 以查看是否有某种方法可以覆盖默认值和大小。在摇摆(至少对我而言),setMinimum,setMaximum,setdefault,setDefaultOnThursday,...时,尺寸可能会令人困惑。可能有办法。如果您可以联系图书馆设计者,您可能会找到一个可以减轻令人不快的黑客行为的答案。

  2. 也许扩展工厂只覆盖一些默认的尺寸参数?取决于工厂,但它可能是可能的。

创建具有相同名称的类可能是唯一的其他选择,正如其他人指出的那样,它很丑陋,当您更新 api 库或部署在不同的环境中时,您很可能会忘记它并破坏东西,忘记为什么拥有classpath 就是这样设置的。

于 2008-12-19T15:22:27.247 回答
0

您可以尝试使用 PowerMock/Mockito 等工具。如果您可以在测试中进行模拟,那么您也可以在生产中进行模拟。

但是,这些工具并不是真正设计为以这种方式使用的,因此您必须自己准备环境,并且无法像在测试中那样使用 JUnit 运行器...

于 2015-10-08T10:36:49.473 回答
-1

好吧,我一直在尝试发布建议,然后我发现它们不起作用,或者您已经提到过您尝试过它们。

我能想到的最好的解决方案是继承WindowDisplayFactory,然后在子类的createView()方法中,先调用super.createView(),然后修改返回的对象,把widget完全扔掉,换成一个子类的实例做你想做的事。但是这个小部件是用来初始化东西的,所以你必须去改变所有这些。

然后我想到对从 createView() 返回的对象使用反射并尝试以这种方式修复问题,但同样,这很麻烦,因为使用小部件初始化了很多东西。不过,我想我会尝试使用这种方法,如果它足够简单,可以证明复制和粘贴的合理性。

我会看这个,再加上想看看我是否能想出任何其他的想法。Java 反射当然很好,但它无法击败我在 Perl 和 Python 等语言中看到的动态自省。

于 2008-12-19T15:43:47.763 回答