如何在Java中实现具有内部依赖性的惰性求值状态类?

时间:2012-02-10 12:19:03

标签: java design-patterns

我正在编写一个财务计算类,它将有许多setter函数输入,一些私有中间值和一些getter函数作为输出。

  • 私有中间值仅取决于输入值。

  • 输出值(由公共getter访问)仅取决于输入和中间值。

最终你可以将整个事物描绘成一个有点纠结的非循环有向图,一边是一堆输入,最后流到右边的一堆输出。

实现此课程的最佳方法是什么。我有一些特殊要求:

  • 在可能的情况下,懒惰评估。当输入发生变化时,我们现在可以知道可能需要哪些输出。

  • 该类必须易于重新设计,因此某种声明性模型会被优先考虑。

理想情况下,我希望能够说C取决于A和B.如果在A或B改变之后请求C,那么它将知道需要重新计算C,否则C将永远不需要待刷新。

我有一个Java模式可以帮助我干净地实现这种计算器吗?

4 个答案:

答案 0 :(得分:2)

你可以使用这样的模式。

double[] inputs = { ... }
double[] previousInputs = { Double.NaN, etc }; // NaN is never equal.
double[] outputs = 

public void update() {
   if (!Arrays.equals(inputs, previousInputs)) {
       recalculate(inputs, outputs);
       copyTo(inputs, previousInputs);
   }
}

答案 1 :(得分:2)

好像你有某种实时流处理问题。

看一下twitter storm。即使您决定不使用它,您也可以借用tutorial page中解释的一些概念。

答案 2 :(得分:2)

您可以通过创建可重新计算的未来值来构建解决方案。

public class Computation<T> {
   private T value;
   private Set<Computation<?>> usedBy;

   public T getValue(Computation<?> getter) {
     if (usedBy == null) {
       // value was not computed
       value = compute();
       usedBy = new HashSet();
     }
     if (getter != null) {
       // add a dependency
       usedBy.add(getter);
     }
     return value;
   }

   protected T compute() {
     // override when needed a lazily-computed value
     return null;
   }

   public void setValue(T value) {
     // invalidate this value
     invalidate();
     // set the new value
     this.value = value;
     usedBy = new HashSet();
   }

   public void invalidate() {
     if (usedBy != null) {
       for (Computation<?> c : usedBy) {
         c.invalidate();
       }
       usedBy = null;
     }
     value = null;
   }
}

public class Business {
  private Computation<Integer> a = new Computation<Integer>();
  private Computation<Integer> b = new Computation<Integer>();
  private Computation<Integer> c = new Computation<Integer>() {
    public Integer compute() {
      return a.getValue(this) + b.getValue(this);
    }
  };

  public void setA(int v) {
    a.setValue(v);
  }
  public void setB(int v) {
    b.setValue(v);
  }
  public int getC() {
    return c.getValue(null);
  }
}

它完全是懒惰的并且计算出依赖性。

答案 3 :(得分:1)

就我个人而言,我同意彼得的观点,但为了论证,我还有另外两个答案。我建议查看规则引擎(例如Drools)以实现这样的灵活业务逻辑。它们的设计使得变量之间的更新规则易于设置和随意更改。它们也应该具有相当的性能。

然后,对于DYI-er,这是一个Spring灵感版本。最大的缺点是您将依赖项作为列表。您可以轻松使用HashMap,但之后您将失去语法安全性。

public abstract class Variable<T> {

  private T currentValue;
  private List<Variable<?>> dependencies = new ArrayList<Variable<?>>();
  private List<Variable<?>> upstream = new ArrayList<Variable<?>>();

  public T get() {
    return currentValue;
  }

      public void set(T newValue) {
    currentValue = newValue;
    updateUpstream();
  }

  public abstract T recalculateValue(List<Variable<?>> dependencies);

  private void update() {
    set(recalculateValue());
  }

  private void updateUpstream() {
    for(Variable<?> variable : upstream) {
      variable.update();
    }
  }

  private void addUpstream(Variable<?> variable) {
    upstream.add(variable);
  }

  public void setDependencies(List<Variable<?>> dependencies) {
    this.dependencies = dependencies;
    for(Variable<?> variable) {
      variable.addUpstream(this);
    }
  }

}

相应的applicationContext.xml如下所示:

<bean id="A" class="com.app.AVariable"/>
<bean id="B" class="com.app.BVariable"/>
<bean id="C" class="com.app.CVariable">
  <property name="dependencies">
    <list>
      <ref bean="A"/>
      <ref bean="B"/>
    </list>
  </property>
</bean>

为了获得额外的功劳,您可以实现bean后处理器,以根据注释自动计算和设置依赖项。例如:

public class CVariable extends Variable<Integer> {
  private AVariable a;
  private BVariable b;

  @Dependency
  public void setA(AVariable a) {
    this.a = a;
  }

  @Dependency
  public void setB(BVariable b) {
    this.b = b;
  }

  //If you were going this route you wouldn't need the list of dependencies
  public Integer recalculateValue() {
    return a.get() + b.get();
  }

}