【问题标题】:Constructor to interface/abstract class using Java generics使用 Java 泛型的接口/抽象类的构造函数
【发布时间】:2015-11-02 10:43:20
【问题描述】:

请注意更新,我的问题没有明确表述。很抱歉。

假设我们有以下代码:

class Foo extends/implements AnAbstractClass/AnInterface { /* to make sure the constructor with int as input is implemented */ 
    Foo(int magicInt) { magicInt + 1; /* do some fancy calculations */ }
}

class Bar extends/implements AnAbstractClass/AnInterface { /* to make sure the constructor with int as input is implemented */ 
    Bar(int magicInt) { magicInt + 2; /* do some fancy calculations */ }
}

class Factory<T extends/implements AnAbstractClass/AnInterface> {
    int magicInt = 0; 

    T createNewObject() {
        return new T(magicInt) // obviously, this is not working (*), see below
    }
}

/* how it should work */
Factory<Foo> factory = new Factory<Foo>();
factory.createNewObject() // => Foo with magicInt = 1

Factory<Bar> factory = new Factory<Bar>();
factory.createNewObject() // => Bar with magicInt = 2

(*)位置我不知道该怎么办。我怎样才能确保具有这样签名的构造函数 ...(int magicInt) 已实现?我无法定义

  1. 接口中具有特定签名的构造函数

    interface AnInterface {
        AnInterface(int magicInt);
    }
    
  2. 一个执行特定构造函数的抽象类

    abstract class AnAbstractClass {
        abstract AnAbstractClass(int magicInt);
    }
    

    这显然缺少在子类中实现构造函数的要求:

    abstract class AnAbstractClass {
       AnAbstractClass(int magicInt) {}
    }
    
  3. an interfaceabstract class 中的静态方法,可以为 AnInterfaceAnAbstractClass 的每个实现重写(我想到了工厂模式)

要走的路是什么?

【问题讨论】:

  • 我认为你想要的结果代码有些奇怪。 SampleSource 具有扩展 SampleFactory 的参数。然后在getCurrentSample() 中调用这个样本工厂来创建一个应该与SampleFactory 具有相同类型的样本。那么创建一个样本会给你一个样本工厂吗?
  • 好吧,因为接口中允许使用 Java 8 静态方法。
  • 拥有扩展 Sample 并实现 SampleFactory 的类似乎很奇怪...
  • @Flown 他不想在接口中使用静态方法,他希望使用接口来强制实现该接口的类有一个。
  • 由于类型擦除,T 的实际类型在运行时是未知的,所以T.class 将不起作用。

标签: java generics constructor factory-pattern static-constructor


【解决方案1】:

我真的认为你的想法行不通。

我觉得它打破了Factory 模式的概念,它的真正目的是让一个方法负责创建单个类see ref 的实例。

我宁愿:

  1. 在您的工厂类中为您要构造的每种类型的对象提供一个方法
  2. 并且可能不是在构造函数中具有特定行为,而是在父抽象类中具有一个通用构造函数和一个执行花哨计算的抽象方法(但这确实是样式偏好)。

这会导致以下情况:

abstract class AbstractSample {
    private int magicInt;

    public AbstractSample(int magicInt) {
        this.magicInt = magicInt;
    }

    protected int getMagicInt() {
        return magicInt;
    }

    public abstract int fancyComputation();

}

public class Foo extends AbstractSample {
    public Foo(int magicInt) {
        super(magicInt)
    }

    public int fancyComputation() {
        return getMagicInt() + 1;
    }
}

public class Bar extends AbstractSample {
    public Bar(int magicInt) {
        super(magicInt)
    }

    public int fancyComputation() {
        return getMagicInt() + 2;
    }
}

public class SampleFactory {
    private int magicInt = 0;

    public Foo createNewFoo() {
        return new Foo(magicInt);
    }

    public Bar createNewBar() {
        return new Bar(magicInt);
    }
}

之前版本问题的答案 如果更新后的答案满足 OP,则可能会被删除

拥有既扩展 Sample 又实现 SampleFactory 的类肯定很奇怪...

我宁愿有类似的东西:

class Sample { 
    protected Sample() { /* ... */ }
}

interface SampleFactory<T extends Sample> {
    T createSample(final int i);
}

class AccelerationSample extends Sample {
    public AccelerationSample(final int i) { /* do some fancy int calculations*/ }
}

class OrientationSample extends Sample {
    private OrientationSample (final int i) { /* do some fancy int calculations*/ }
}

abstract class SampleSource<T extends Sample> {
    int magicInt; 
    SampleFactory<T> sampleFactory;
    T getCurrentSample() {
       return sampleFactory.createSample(magicInt);
    }
}

class AccelerationSampleSource extends SampleSource<AccelerationSample> {
    SampleFactory<AccelerationSample> sampleFactory = new SampleFactory<> {
       public AccelerationSample createSample(final int i) {
          return new AccelerationSample(i);
       }
    }
}

class OrientationSampleSource extends SampleSource<OrientationSample> {
    SampleFactory<OrientationSample> sampleFactory = new SampleFactory<> {
       public OrientationSample createSample(final int i) {
          return new OrientationSample(i);
       }
    }
}

使用命名工厂会更干净,例如

public AccelerationSampleFactory implements SampleFactory<AccelerationSample> {
    public AccelerationSample createSample(final int i) {
        return new AccelerationSample(i);
    }
 }

然后你可以用作

class AccelerationSampleSource extends SampleSource<AccelerationSample> {
    SampleFactory<AccelerationSample> sampleFactory = new AccelerationSampleFactory();
}   

【讨论】:

  • 确实,对不起...无法在接口中定义静态方法...我已经相应地更新了我的答案。 (如果您希望它是通用的,则不需要该方法是静态的)。
  • (更具体地说,在 java 8 中,你可以在接口中有静态方法,但绝对不能有返回实例类型的静态方法)
  • 感谢您的解决方案,但是我为每个类都有一个静态工厂方法(在我的情况下,我会有几十个这样的方法)。我认为因此引入了泛型。 :)
  • 此外,您的解决方案不再存在多态性。
  • 确实,这就是我在介绍中提到的。工厂模式的目标通常是每个类都有一个工厂方法。有一些多态性,以方法覆盖的形式。泛型在许多情况下都很有用,但工厂模式并不是其中之一,恕我直言。但是,您可以使用自省来调用工厂中的构造函数,但我不太喜欢这种解决方案。
【解决方案2】:

听起来您确实在寻找一种解决方案,以解决如何编写没有一堆 if/else 块并且不在每个类中编写一个的通用工厂方法。因此,请考虑在以下代码中使用反射:

interface Interface {
}

class Foo implements Interface {
    Foo(int magicInt) { magicInt = magicInt + 1; /* do some fancy calculations */ }
}

class Bar implements Interface {
    Bar(int magicInt) { magicInt = magicInt + 2; /* do some fancy calculations */ }
}

class Factory<T extends Interface> {
    int magicInt = 0; 

    public T createNewObject(Class<T> typeToMake) {
        try {
            T t = createNewObjectWithReflection(typeToMake);
            return t;
        } catch (Exception e) {
            throw new RuntimeException("Construction failed!", e);
        }
    }

    private T createNewObjectWithReflection(Class<T> typeToMake) throws Exception {
        // find the constructor of type to make with a single int argument
        Constructor<T> magicIntConstructor = typeToMake.getDeclaredConstructor(Integer.TYPE);
        // call the constructor with the value of magicInt
        T t = magicIntConstructor.newInstance(magicInt);
        return t;
    }
}

/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Factory<Foo> fooFactory = new Factory<Foo>();
        Foo foo = fooFactory.createNewObject(Foo.class);
        System.out.println(foo);

        Factory<Bar> barFactory = new Factory<Bar>();
        Bar bar = barFactory.createNewObject(Bar.class);
        System.out.println(bar);
    }
}

你可以run the demo at IDEOne here

【讨论】:

  • 是的,这就是我正在寻找的解决方案。但是Java有那么丑吗?
  • 差不多,是的。我编辑了代码帖子以整理代码,但一般来说反射并不漂亮。
  • 工厂本身没有理由是通用的。拥有工厂通用的方法就足够了。我还认为,您可以使用 TypeLiteral 和使用辅助注入的 Guice 工厂绑定来实现相同的功能。这使您的代码更简洁。
  • 但是几乎不管你使用什么解决方案,你都需要某种自动反射测试来确保你已经覆盖了所有的用例。否则,可能会创建一个不符合您的工厂期望/要求的新子类,然后您就会崩溃。然而,这种方法的一个严重限制是,如果 Foo 或 Bar 在它们的构造函数中需要不同的参数,它就不再起作用了。如果 Foo 需要 DB 连接,而 Bar 需要 WS 客户端,那么构造函数会有所不同,这种方法不再可行。
  • 感谢您的 cmets Nathan。关于工厂类本身的公平评论,不需要是通用的——我只是试图让所有类尽可能地符合问题中的原始工厂类。我考虑过使用 TypeLiteral,但似乎唯一可行的获取类型信息的方法是将 TypeLiteral 传递给工厂方法来代替类。由于问题在于实例化一个新类,因此传递一个类似乎更简单,因为如果我传入一个 TypeLiteral,我只是用它来取回一个用于调用构造函数的类对象。
【解决方案3】:

正如您所指出的,问题中的 3 个想法均不受支持(接口中具有特定签名的构造函数、强制执行特定构造函数的抽象类或接口或抽象类中的静态方法)

但是,您可以为您最终想要的类型定义一个作为工厂的接口(或抽象类)。

public interface AnInterface {
    int fancyComputation();
}
public interface IFooBarFactory<T extends AnInterface> {
    T create(int magicNumber);
}

IFooBarFactory 有 2 个具体实现

public class BarFactory implements IFooBarFactory<Bar> {
    public Bar create(int magicNumber) {
        return new Bar(magicNumber);
    }
}
public class FooFactory implements IFooBarFactory<Foo> {
    public Foo create(int magicNumber) {
        return new Foo(magicNumber);
    }
}

然后使用策略模式 (https://en.wikipedia.org/wiki/Strategy_pattern) 检索正​​确的工厂。然后使用这个具有已知接口的工厂来制造具有正确值的对象(以及制造对象所需的任何附加值)。

    FooBarFactory fooBarFactory = new FooBarFactory();
    IFooBarFactory<T> factory = fooBarFactory.createFactory(typeOfAnInterface);
    T impl = factory.create(magicNumber);

具体实现

public class Bar implements AnInterface {
    private final int magicInt;
    public Bar(int magicInt) {
        this.magicInt = magicInt;
    }
    public int fancyComputation() {
        return magicInt + 2;
    }
}
public class Foo implements AnInterface {
    private final int magicInt;
    public Foo(int magicInt) {
        this.magicInt = magicInt;
    }
    public int fancyComputation() {
        return magicInt + 1;
    }
}

以下代码:

public static void main(String ... parameters) {
    test(Foo.class);
    test(Bar.class);
}
private static <T extends AnInterface> void test(Class<T> typeOfAnInterface) {
    T impl = createImplForAnInterface(typeOfAnInterface, 10);
    System.out.println(typeOfAnInterface.getName() + " produced " + impl.fancyComputation());
}
private static <T extends AnInterface> T createImplForAnInterface(Class<T> typeOfAnInterface, int magicNumber) {
    FooBarFactory fooBarFactory = new FooBarFactory();
    IFooBarFactory<T> factory = fooBarFactory.createFactory(typeOfAnInterface);
    T impl = factory.create(magicNumber);
    return impl;
}

打印

Foo produced 11
Bar produced 12

与具有自省或静态工厂的解决方案相比,这提供了许多好处。调用者不需要知道如何制造任何对象,调用者也不需要知道或关心什么时候方法是“正确”的方法来检索正确的类型。所有调用者只需调用一个公共/已知组件,该组件返回“正确”工厂。这使您的调用者更清晰,因为它们不再与 FooBar 类型的 AnInterface 的具体实现紧密耦合。他们只需要关心“我需要一个 AnInterface 的实现,它消费(或处理)这种类型”。我知道这意味着你有两个“工厂”类。一个检索正确的工厂,另一个负责创建具体类型 Foo 和 Bar。但是,您可以通过额外的抽象层向调用者隐藏此实现细节(请参阅 createImplForAnInterface 方法)。

如果您通常使用某种形式的依赖注入,这种方法将特别有用。我的建议与 Guice 的辅助注入 (https://github.com/google/guice/wiki/AssistedInject) 或 Spring 中的类似想法 (Is it possible and how to do Assisted Injection in Spring?) 完全对应。

这意味着您需要有多个工厂类(或 Guice 的依赖注入绑定规则),但这些类中的每一个都很小、简单且易于维护。然后编写一个小测试,检索所有实现 AnInterface 的类,并验证实现策略模式的组件是否涵盖了所有情况(通过反射 - 我将使用 org.reflections:reflections 中的 Reflections 类)。这为您提供了一个可用的代码抽象,通过减少冗余代码、放松组件的紧密耦合以及不牺牲多态性来简化这些对象的使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-20
    • 2012-01-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多