【问题标题】:Best design pattern to support static functions of generic classes that implements interface支持实现接口的泛型类的静态功能的最佳设计模式
【发布时间】:2018-05-02 18:52:54
【问题描述】:

我有一个接口Operations:

public interface Operations{

    Operations add(Operations op);

    Operations subtract(Operations op);
}

这个界面应该允许用户选择如何进行简单的计算。例如,如果用户只想使用数据类型Integer 及其基本操作,他可以​​使用类MyInteger:

// simple implementation without type check etc..
public final class MyInteger implements Operations{
    private final int delegate;

    public MyInteger(int i){
        delegate = i;
    }

    @Override
    Operations add(Operations other){
        return new MyInteger(delegate + ((MyInteger) other).delegate);
    }
    @Override
    Operations subtract(Operations other){
        return new MyInteger(delegate - ((MyInteger) other).delegate);
    }
    (...)
}

在其他类中,将根据接口Operations 的功能进行计算。但有时我需要一个静态上下文,如 valueOf(val) 函数,它为给定的 String、double、int 等返回相应类的实现,如下例所示:

public class Calculation<D extends Operations> {

    private final D value1, value2;
    private final D value3 = D.valueOf(4);  // cannot implement this in interface 

    public Calculation(D value1, D value2){
        this.value1 =value1;
        this.value2 = value2;
    }

    public D sumPlusValue3(){
        return value1.plus(value2).plus(value3);
    }
}

所以

Calculation<MyInteger> calc = new Calculation<MyInteger>(new MyInteger(1), new MyInteger(2));
Operations result = calc.sumPlusValue3();

一般来说,我认为我必须使用像 Builder 或 Factory 这样的设计模式来允许用户指定他想要的实现,比如

Operations integerImpl = OperationsBuilder.create("Integer");

但在研究了这些模式之后,我也找不到一种干净的方法来在Calculation 类中为value3 实现泛型类D。

编辑 1:

我编辑了问题并标记了来自@Stefan Haustein 的答案,因为这是解决我的问题的最佳方法。 但@davidxxx 的回答也是关于我最初的问题的一个很好的回答 - 如何only 表示valueOf(int val) 函数。

【问题讨论】:

  • 我想在double 中添加int,可以吗?考虑Operation&lt;OPERAND1, OPERAND2, RESULT&gt;
  • 不,这应该是不可能的。这意味着必须验证所有操作实现都属于同一实例。在 Calculations 类中,这是因为泛型参数而满足的,或者?

标签: java generics design-patterns interface


【解决方案1】:

1) 初始问题:工厂调用

这里:

private final D value3 = D.valueOf(4);  // cannot implement this in interface 

您希望使用非工厂泛型的值 value3。
在接口中定义valueOf()(作为实例方法)也不是一个解决方案。

作为替代方案,您可以在 Calculation 构造函数中引入工厂参数,以便为 value3 赋值。

所以替换:

private final D value3 = D.valueOf(4);

public Calculation(D value1, D value2){
    this.value1 =value1;
    this.value2 = value2;
}

作者:

private final D value3; 

public Calculation(D value1, D value2, FactoryD factoryD){
    this.value1 =value1;
    this.value2 = value2;
    this.value3 = factoryD.create(4);
}

或者更好的使用工厂的Function功能接口:

private final D value3; 

public Calculation(D value1, D value2, IntFunction<D> factoryD){
    this.value1 =value1;
    this.value2 = value2;
    this.value3 = factoryD.apply(4);
}

你可以这样使用它:

Calculation<MyInteger> calc = new Calculation<>(new MyInteger(1), new MyInteger(2), MyInteger::new );
Operations result = calc.sumPlusValue3();

但是Operations 漏掉了一些东西。

2) 无法表示Operations 值。

其实Operations定义了操作方法但是没有办法显示对象值:

public interface Operations{

    Operations add(Operations op);

    Operations subtract(Operations op);
}

您应该定义一种获取值的方法并使接口通用以处理任何类型的值:

public interface Operations<T> {

    Operations<T> add(Operations<T> op);

    Operations<T> subtract(Operations<T> op);

    T getValue();

}

MyInteger 可以这样定义:

public final class MyInteger implements Operations<Integer> {

    private final int delegate;

    public MyInteger(int i) {
      delegate = i;
    }

    @Override
    public Operations<Integer> add(Operations<Integer> other) {
      return new MyInteger(delegate + other.getValue());
    }

    @Override
    public Operations<Integer> subtract(Operations<Integer> other) {
      return new MyInteger(delegate - other.getValue());
    }

    @Override
    public Integer getValue() {
      return delegate;
    }
}

而Calculation 可以用两个泛型来定义:一个用于Operations,另一个用于Operations 返回类型:

public class Calculation<C, D extends Operations<C>> {

    private final D value1, value2;
    private final D value3; 

    public Calculation(D value1, D value2, IntFunction<D> factoryD){
        this.value1 =value1;
        this.value2 = value2;
        this.value3 = factoryD.apply(4);
    }

    public Operations<C> sumPlusValue3(){
        return value1.add(value2).add(value3);
    }        
}

你可以这样使用这段代码:

Calculation<Integer, MyInteger> calc = new Calculation<>(new MyInteger(1), new MyInteger(2), MyInteger::new );
Operations<Integer> result = calc.sumPlusValue3();
System.out.println(result.getValue());

输出:

7

【讨论】:

  • 感谢返回类型提示。我也喜欢使用通用函数/IntFunction 的方法。对我来说缺点是我有一个额外的参数,我可能需要一些其他的转换,例如 D valueOf(String val), D valueOf(Int val), ... 你会说实例函数更适合这个案子?
  • 不客气。我建议IntFunction,因为您在Calculation 中硬编码数值以创建D 实例:this.value3 = factoryD.apply(4); 如果您希望能够传递任何类型来构建D 实例,您可以指定Function&lt;C, D&gt; 函数。当然,在Calculation 类中,您应该引用C 类型而不是数字或String 值,因为Function 中的C 类型是通用类型而不是特定类型。
【解决方案2】:

如果在Calculation 中你总是在附近有一个D 的实例,你可以让valueOf 成为Operations 的非静态成员,尽管从技术上讲你不需要相应的值。

如果不能为valueOf 使用“虚拟”实例,您可以要求Class&lt;D&gt; 作为Calculations 的构造函数参数。这将允许您调用newInstance(),也许提供一个中性元素——但您仍然需要诸如addInt 之类的操作来获得具体值。可以将这些想法结合起来,以避免需要明确的虚拟值。

另一种选择是在Calculation 中使 valueOf 抽象,避免对虚拟实例的依赖,将负担转移到 Calculation 实例的创建:

Calculation<MyInteger> calc = new Calculation<MyInteger>(
    new MyInteger(1), new MyInteger(2)) {
      public MyInteger valueOf(int i) {
        return new MyInteger(i);
      }
    });

Operations result = calc.sumPlusValue3();

总的来说,这似乎是关于“纯度”和“额外层/复杂性”之间的正确平衡。我会倾向于更加务实并避免这里的开销——以牺牲纯度为代价。

附:另一种选择可能是有一个显式的工厂接口,但按照惯例将其存储为 Operations 类的一个字段:

public final class MyInteger implements Operations{
  private final int delegate;

  public static final OperationsFactory<MyInteger> FACTORY = 
    new OperationsFactory<>() {
      @Override
      MyInteger valueOf(int i) {
        return new MyInteger(i);
      }
    };

  public MyInteger(int i){
      delegate = i;
  }

  @Override
  Operations add(Operations other){
      return new MyInteger(delegate + ((MyInteger) other).delegate);
  }
  @Override
  Operations subtract(Operations other){
    return new MyInteger(delegate - ((MyInteger) other).delegate);
  }
  (...)
}

这至少可以将所有内容的实现部分放在一个文件中...

【讨论】:

  • 如果我必须使用几个 valueOf 函数,例如: D valueOf(String val), D valueOf(double val) 你会说最好将它们作为实例函数并将它们的标题放在接口上使用通用参数(如@davidxxx 答案)?我不知道,但是在存储其他值的实例上使用“valueOf(val)”函数看起来有点脏……
  • 如果太多,我会拆分一个工厂接口。鉴于您只需要一个实例,您可以将其作为静态字段放入实现中,请参阅添加的示例
  • 但是在我的'Calculate'类中我不知道'D'的实现,所以我不知道我应该使用哪个FACTORY?例如,除了我不知道是否应该调用 'MyDouble.FACTORY.valueOf(..)' 或 MyInteger.FACTORY.valueOf(..)
  • 工厂需要作为计算的附加构造函数参数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多