Java中Decorator模式的替代方案?

thk*_*ala 8 java oop

假设您具有以下与统计相关的类的层次结构,其结构类似于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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 创建子类S0C,S1C,S2CS3CS0,S1,S2S3与检查收敛的代码的副本分别,每个:

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

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

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

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

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

编辑:

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

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

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

  • 我想保持我的接口模块化.这是我的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 ...;
       }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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 ...;
       }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  • 汇聚检查装饰器将是这样的:

    class CC<T extends S> implements S {
       T o = ...;
    
       int addSample(double x) {
          o.addSample(x);
          ...;
       }
    
       double getMean() {
          return o.getMean();
       }    
    
       boolean hasConverged() {
          return ...;
       }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    • 实现相同的接口 CC
    • 使用CC与其包装对象类型相同的界面...
    • ......这迫使我在使用该界面的S*方法,如果我想能够使用NBS*对象不使用CC
  • 我选择的装饰师模式只是因为缺乏更好的选择.这是迄今为止我发现的最干净的解决方案.

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

Dar*_*Teo 4

基于您最近的编辑。

正如您可能已经意识到的那样,装饰器不适合于此。这是因为它解决的是单个功能的增强,而不是整个类树的增强。

实现这一目标的一种可能方法是采用策略。策略以算法为重点;它允许你解耦行为代码(很抱歉,如果这里和那里有一些 C# 的漏洞)


样本班

public class S {
   private List<Integer> Samples = new List<Integer>(); 

   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<IOp> operations){
      for (Op operation : operations){
          Process(operation);
      }
   }
   public void Process(IOp operation){
      operation.Compute(this.Samples);
   }
}
Run Code Online (Sandbox Code Playgroud)

运营

public interface IOp { 
   // Interface is optional. Just for flexibility. 
   public void Compute(List<Integer> data);
}
public class Op<T> 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<Integer> data);
}
class ComputeMeanOperation extends Op<double>{
   public void Compute(List<Integer> data){
       /* sum and divide to get mean */
       this.Result = /* ... */
   }
}
class CheckConvergenceOperation extends Op<boolean>{
   public void Compute(List<Integer> data){
       /* check convergence */
       this.Result = /* ... */
   }
}
Run Code Online (Sandbox Code Playgroud)

用法

public static void main(String args[]) {
    S s = new S();
    s.addSample(1);
    /* ... */

    ComputeMeanOperation op1 = new ComputeMeanOperation();
    CheckConvergenceOperation op2 = new CheckConvergenceOperation ();        

    // Anonymous Operation
    Op<Integer> op3 = new Op<Integer>(){
       public void Compute(List<Integer> 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);
}
Run Code Online (Sandbox Code Playgroud)

优点:

  • 您可以根据需要任意添加和删除操作。
  • 对示例类没有额外的更改。
  • 样本类是内聚数据结构。
  • 模块化:每个操作都是独立的。接口仅公开所需的内容。与每个操作交互的通用流程。
  • 如果您出于某种原因需要重复执行此操作,则可以将所有操作存储在一个数组中,并在循环中重用它。比调用 4-5 个方法并存储结果要干净得多。

缺点/限制:

  • 如果您的操作需要大量数据,那么您将必须将该数据公开给您的操作,从而增加耦合(如果您需要,我可以编辑帖子)。在我的示例中,我只是传递了一个示例列表。如果需要,您可能必须传递整个数据结构。
  • 如果您有任何操作依赖于另一个操作的结果,则这将无法开箱即用。(这可以使用 Composite 来完成 - 一个由多个 Op 组成的大型 Op,其结果将传递到下一个 Op。)

希望这符合您的要求:)