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 }
现在假设我们想要扩展这些类中的每一个,例如检查度量的收敛。 出于我的目的,我不需要在运行时进行此扩展。 我可以想到以下替代方案:
-
分别从
S0
,S1
,S2
和S3
创建子类S0C
,S1C
,S2C
和S3C
,每个子类都有一个检查收敛的代码副本:- 优点:
- 概念上直截了当
- 生成的对象仍属于超类
- 子类源代码仅包含额外的收敛检查代码
- 缺点:
- 大量的代码重复 – 将来产生的更改同步开销
- 主要缺点:
- 如果我想要另一组类例如预处理样本怎么办? 我们正在谈论相同代码的指数式复制!
- 优点:
-
使用装饰器模式 :
- 优点:
- 没有代码重复!
- 缺点:
- 对象不再属于原始类(易于解决)
- 由于使用了虚方法调用,而不是特殊的方法调用,因此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
情况下将NB
与S*
对象一起使用,那么迫使我将该接口用于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; }