Java中Decorator模式的替代方案?

假设您具有以下与统计相关的类的层次结构,其结构类似于Template方法模式 :

interface S { // Method definitions up-to and including the S3 class } class S0 implements S { // Code that counts samples } class S1 extends S0 { // Code that calls the superclass methods and also computes the mean } class S2 extends S1 { // Code that calls the superclass methods and also computes the variance } class S3 extends S2 { // Code that calls the superclass methods and also computes the skewness } 

现在假设我们想要扩展这些类中的每一个,例如检查度量的收敛。 出于我的目的,我不需要在运行时进行此扩展。 我可以想到以下替代方案:

  • 分别从S0S1S2S3创建子类S0CS1CS2CS3C ,每个子类都有一个检查收敛的代码副本:

    • 优点:
      • 概念上直截​​了当
      • 生成的对象仍属于超类
      • 子类源代码仅包含额外的收敛检查代码
    • 缺点:
      • 大量的代码重复 – 将来产生的更改同步开销
    • 主要缺点:
      • 如果我想要另一组类例如预处理样本怎么办? 我们正在谈论相同代码的指数式复制!
  • 使用装饰器模式 :

    • 优点:
      • 没有代码重复!
    • 缺点:
      • 对象不再属于原始类(易于解决)
      • 由于使用了虚方法调用,而不是特殊的方法调用,因此Java中的性能非常轻微(它存在!我测量它!)。 这不是很重要,但仍然很明显。
    • 主要缺点:
      • 大量必须与包装对象接口保持同步的委托方法。 使用接口确保不会遗漏任何方法,但即使使用自动生成委托方法的IDE,它仍然很难维护。
      • 要拥有正确实现的装饰器模式,所有装饰器和包装类需要实现完全相同的接口。 这实际上意味着我必须将收敛检查方法添加到S接口,这完全破坏了任何模块化感。 解除此要求的唯一方法是禁止在我的代码中嵌套装饰器。

如果Java支持多重inheritance,我可能已经能够通过inheritance统计信息和基本收敛检查(或其他)类来处理这种情况。 唉,Java不支持多重inheritance(不,接口不计算!)。

有没有更好的方法来处理Java中的这个问题? 也许是不同的设计模式? 更技术的解决方案? 某种特殊的仪式舞蹈?

PS:如果我误解了某些东西,请随意(轻轻地)指出它……

编辑:

我似乎需要澄清一下我的目标:

  • 我不需要运行时对象组合。 我想要的是用新方法扩展S*类的function。 如果我可以根据需要创建子类而不需要代码重复,我可能会这样做。 如果我能在使用地点(不太可能)这样做,甚至更好。

  • 我宁愿不要一遍又一遍地写相同的代码。 注意:委托方法和构造函数很好,我想,实现算法的方法不是。

  • 我想保持我的接口模块化。 这是我的Decorator模式的主要问题 – 除非放置非常特定的嵌套约束,否则最终会得到所有接口的超级接口…

编辑2:

要解决一些意见:

  • S*类使用模板方法构建:

     class S0 { int addSample(double x) { ...; } double getMean() { return Double.NaN; } } class S1 extends S0 { int addSample(double x) { super.addSample(x); ...; } double getMean() { return ...; } } 
  • 我从第一个解决方案扩展的S*C类是这样的:

     interface S { int addSample(double x); double getMean(); } class S0C extends S0 implements S { int addSample(double x) { super.addSample(x); ...; } boolean hasConverged() { return ...; } } class S1C extends S1 { int addSample(double x) { super.addSample(x); ...; } boolean hasConverged() { return ...; } } 

    请注意hasConverged()方法的重复。

  • 收敛检查装饰器将是这样的:

     class CC implements S { T o = ...; int addSample(double x) { o.addSample(x); ...; } double getMean() { return o.getMean(); } boolean hasConverged() { return ...; } } 

    问题:如果我想除收敛检查之外还要组合另一个分隔符行为,我需要一个单独的装饰器,例如NB – 并且为了能够访问例如hasConverged()方法,新的装饰器需要:

    • 实现与CC相同的接口
    • 对于包装对象类型,使用与CC相同的接口…
    • …如果我希望能够在不使用CC情况下将NBS*对象一起使用,那么迫使我将该接口用于S*方法
  • 我选择的Decorator模式只是因为缺乏更好的选择。 这是迄今为止我发现的最干净的解决方案。

  • 扩展S*类时,我仍然需要完整的原件。 将汇聚function放在一个共同的超类中意味着相关的行为(及其性能影响)现在将存在于所有子类中,这绝对不是我想要的。

根据您最近的编辑。

你可能已经意识到,装饰师不适合这个。 这是因为它解决的是增加单个function,而不是增加整个类树。

可能实现这一目标的方法是采用策略。 战略以算法为重点; 它允许你解耦行为代码(对不起,如果有一点C#在这里和那里滑倒)


样本类

 public class S { private List Samples = new List(); public void addSample(int x){ Samples.Add(new Integer(x)); } public void Process(IOp[] operations){ for (Op operation : operations){ Process(operation); } } public void Process(ICollection operations){ for (Op operation : operations){ Process(operation); } } public void Process(IOp operation){ operation.Compute(this.Samples); } } 

操作

 public interface IOp { // Interface is optional. Just for flexibility. public void Compute(List data); } public class Op implements IOp{ // Generics is also optional. I use this to standardise data type of Result, so that it can be polymorphically accessed. // You can also put in some checks to make sure Result is initialised before it is accessed. public T Result; public void Compute(List data); } class ComputeMeanOperation extends Op{ public void Compute(List data){ /* sum and divide to get mean */ this.Result = /* ... */ } } class CheckConvergenceOperation extends Op{ public void Compute(List data){ /* check convergence */ this.Result = /* ... */ } } 

用法

 public static void main(String args[]) { S s = new S(); s.addSample(1); /* ... */ ComputeMeanOperation op1 = new ComputeMeanOperation(); CheckConvergenceOperation op2 = new CheckConvergenceOperation (); // Anonymous Operation Op op3 = new Op(){ public void Compute(List samples){ this.Result = samples[0]; // Gets first value of samples } } s.Process(op1); // Or use overloaded methods s.Process(op2); s.Process(op3); System.out.println("Op1 result: " + op1.Result); System.out.println("Op2 result: " + op2.Result); System.out.println("Op3 result: " + op3.Result); } 

优点:

  • 您可以根据需要随意添加和删除操作。
  • 样本类没有额外的更改。
  • 样本类是有凝聚力的数据结构。
  • 模块化:每个操作都是自包含的。 界面只暴露所需内容。 与每个操作交互的常用过程。
  • 如果您出于某种原因需要重复执行此操作,则可以将所有操作存储在数组中,并在循环中重用它。 比调用4-5方法和存储结果更清洁。

缺点/限制:

  • 如果您的操作需要大量数据,那么您将不得不将这些数据公开给您的操作,从而增加耦合(如果您需要,我可以编辑post)。 在我的例子中,我只是传递了一个样本列表。 如果需要,您可能必须传入整个数据结构。
  • 如果您有任何操作依赖于另一个操作的结果,这将无法开箱即用。 (这可以使用Composite来实现 – 一个由几个Ops组成的超级Op,其结果传递给下一个。)

希望这符合您的要求:)

我糊涂了。 目前尚不清楚为什么需要第一个inheritance树。 像下面的代码可以做的工作:

 public class Statistics { void add(final double x) { sum += x; sum2 += x * x; sum3 += x * x * x; n++; } double mean() { return n != 0 ? sum / n : 0; } double variance() { return n != 0 ? ( sum2 - sum * sum / n) / (n - 1) : 0; } // add method for skewness double sum, sum2, sum3; int n; }