繁体   English   中英

Java中的Decorator模式的替代方法?

[英]An alternative for the Decorator pattern in Java?

假设您具有以下与统计信息相关的类的层次结构,其结构类似于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支持多重继承,那么我可能可以通过从统计信息和基本的收敛性检查(或其他)类继承来处理此问题。 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 ...; } } 
  • 我的第一个解决方案的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<T extends S> 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*方法使用该接口
  • 我选择装饰器模式只是因为缺少更好的选择。 到目前为止,这只是我发现的最干净的解决方案。

  • 扩展S*类时,我仍然需要完整的原始文件。 将收敛功能放在通用的超类中,将意味着相关的行为(及其性能影响)现在将存在于所有子类中,这绝对不是我想要的。

根据您最近的编辑。

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

可以通过策略来实现这一目标。 策略以算法为重点; 它允许您解耦行为代码(对不起,如果在这里和那里有一些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);
   }
}

运作方式

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 = /* ... */
   }
}

用法

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);
}

优点:

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

缺点/局限性:

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

希望这符合您的要求:)

我糊涂了。 尚不清楚为什么需要第一个继承树。 像下面的代码这样的东西可以做到这一点:

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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM