【问题标题】:Anonymous class of generic type with method overloading具有方法重载的泛型匿名类
【发布时间】:2018-01-29 04:57:04
【问题描述】:

我正在尝试构建T extends BaseClient 类型的对象实例工厂,返回的实例具有T 的方法之一重载以执行一些额外的逻辑。我不能修改 T 类的代码(这就是我需要重载的原因),也不能修改基本抽象类 BaseClient 的代码。

使用具体的、定义明确的类型很容易做到这一点。例如,给定一个FooClient extends BaseClient,我可以这样做:

public class FooClientFactory {
    public static FooClient create() {
        return new FooClient() {
            @Override
            public void theMethod() {
                super.theMethod();
                someAdditionalLogic();
            }
        };
    }
}

但在我需要处理可能有许多不同类型的客户(BaseClient 的所有子类)的世界中,我希望拥有这样的东西:

public class ClientFactory<T extends BaseClient> {
    public static T create() {
        return ...;
    }
}

所以我不必为每个具体的子类复制上面的代码。

很遗憾,不能写new T() { ... }。也不能使用Class&lt;T&gt; 并在其上调用newInstance(),因为这不允许我覆盖theMethod() 的行为。使用Proxy 实例将允许我拦截方法调用并匹配方法名称(丑陋)以在需要时执行someAdditionalLogic(),但因为BaseClient 不是一个接口——我不能让它成为一个接口-- 我无法构建代理(更准确地说,在我的实际用例中,我确实有一个可以使用的接口,特定于每个客户端,但是有些地方需要将返回的实例转换为 @987654337 @,由于代理实例的工作方式,返回的 Proxy 实例没有“技术上”实现/宣传,导致 ClassCastExceptions)。

那么,什么给了?这在 Java 中是完全可能的,还是为每个 FooClientFactory 复制粘贴 XXClient 输入我唯一的选择?

【问题讨论】:

  • 是否可以传递Class 对象并调用newInstance(),根据this answer,是一种选择吗?
  • 正如我在问题的倒数第二段中所说,很遗憾,没有。它确实允许我创建一个实例,但我仍然无法覆盖 theMethod()s 的行为。
  • 我猜MyClient的意思是FooClient
  • 是的,谢谢,在写我的问题时来回走动。现已修复。
  • 如果您正在寻找 new T() { ... override ... } 解决方案,这在 Java 中是不可能的,对此没有争议(使用诸如 BCEL 之类的工具库的“过度杀伤”例外)。我知道您在这里的实际意图是最大化代码共享或更一般地最小化代码行。我认为 lambda 可能是您在不改变 BaseClient/Factory 框架的情况下最接近的。

标签: java generics


【解决方案1】:

这个解决方案可能并不比 cmets 中讨论的更理想。但是,如果组合是您系统中可接受的解决方案,您可能会在诉诸检测之前考虑这样的事情:

public class WrapperClient<T extends BaseClient> extends BaseClient {
    T realClient;

    public WrapperClient(T realClient) {
        this.realClient = realClient;
    }

    public void theMethod() {
        realClient.theMethod();
        someAdditionalLogic();
   }
}

那么对于工厂来说,有一个抽象层:

public abstract class WrapperClientFactory<T extends BaseClient> {
    public final WrapperClient<T> create() {
        T realClient = createRealClient();
        return new WrapperClient<T>(realClient);
    }

    protected abstract T createRealClient();
}

那么工厂实现可能是这样的

public class FooClientFactory extends WrapperClientFactory<FooClient> {
    protected FooClient createRealClient() {
        return new FooClient();
    }
}

然后消费者就会这样做:

BaseClient client = FooClientFactory.create();
client.theMethod();

这里,client 实际上是 WrapperClient 的一个实例,它又会委托给 FooClient,而不是 FooClient 本身,所以它有点 hacky。但它允许您在抽象层的任意子类中扩展逻辑。

如果消费者直接引用他们实际使用的子类并不重要,那么与 cmets 中讨论的某些内容相比,我更喜欢这种方法。

如果这种方法基本上没问题,但客户端子类需要能够提供与纯 BaseClient 不同的接口,则可以添加另一层并使 WrapperClient 抽象。

这种方法的另一个缺点是您会丢失工厂中的静态方法。如果这在您的系统中很重要,您可以创建一个工厂工厂,对消费者隐藏实例化。

【讨论】:

    【解决方案2】:

    如果 Ethan 的答案不是您想要的,那么简短的回答是“不,核心 Java 中没有任何东西可以让您做您想做的事情”。

    话虽如此,这并非不可能,但您需要使用字节码操作库。这就是 Spring 能够代理所有非接口 bean 的方式(如果您不知道,Spring 中的所有注入 bean 实际上都是代理),它使用 CGLIB(source)。基于this other Stackoverflow answer,也可以使用Javasist 代理非接口类;后者似乎相当简单,接近“正常”代理的外观。

    【讨论】:

      猜你喜欢
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-18
      • 2011-06-26
      相关资源
      最近更新 更多