【发布时间】:2011-10-09 23:48:51
【问题描述】:
假设您有以下统计相关类的层次结构,其结构类似于Template method pattern:
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和S2和S3创建子类S1C、S1C、S2C和S3C,每个都有一个检查收敛的代码副本:- 优点:
- 概念上直截了当
- 生成的对象仍然属于超类
- 子类源代码只包含额外的收敛检查代码
- 缺点:
- 大量重复代码 - 未来会产生更改同步开销
- 主要缺点:
- 如果我想要另一组类,例如预处理样品?我们谈论的是相同代码的指数复制!
- 优点:
-
- 优点:
- 没有代码重复!
- 缺点:
- 对象不再属于原始类(很容易解决)
- 由于使用了虚拟方法调用,而不是特殊的方法调用,Java 的性能非常 轻微(它存在!我测量过!)。这不是很重要,但仍然很明显。
- 主要缺点:
- 大约有无数的委托方法必须与封装的对象接口保持同步。使用接口确实可以确保不会遗漏任何方法,但仍然难以维护,即使使用自动生成委托方法的 IDE。
- 要正确实现装饰器模式,所有装饰器和包装类都需要实现完全相同的接口。这实质上意味着我必须添加例如
S接口的收敛检查方法,这完全破坏了任何模块化感。 解除此要求的唯一方法是在我的代码中禁止嵌套装饰器。
- 优点:
如果 Java 支持多重继承,我可能能够通过从统计信息和基本收敛检查(或其他)类继承来处理这个问题。唉,Java 不支持多重继承(不,接口不算在内!)。
有没有更好的方法在 Java 中处理这个问题?也许是不同的设计模式?更技术性的解决方案?某种特殊的仪式舞蹈?
PS:如果我误解了什么,请随时(温和地)指出......
编辑:
看来我需要澄清一下我的目标:
我不需要运行时对象组合。我想要的是用新方法扩展
S*类的功能。如果我可以根据需要创建子类而无需重复代码,我可能会这样做。如果我能在使用地点(不太可能)做到这一点,那就更好了。我不想一遍又一遍地编写相同的代码。注意:委托方法和构造函数很好,我想,实现算法的方法不是。
我想保持我的接口模块化。这是装饰器模式的主要问题 - 除非放置非常具体的嵌套约束,否则您最终会得到所有接口的超级接口......
编辑 2:
针对一些 cmets:
-
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的情况下将NB与S*对象一起使用,这将迫使我将该接口用于S*方法
- 实现与
我选择 Decorator 模式只是因为没有更好的选择。这是迄今为止我找到的最干净的解决方案。
在扩展
S*类时,我仍然需要完整的原件。放例如通用超类中的收敛功能意味着相关行为(及其性能影响)现在将存在于所有子类中,这绝对不是我想要的。
【问题讨论】:
-
为什么我总觉得你可以试试策略模式 (en.wikipedia.org/wiki/Strategy_pattern)?不确定它是否合适,将不得不考虑更多但值得一试......
-
我也倾向于这方面的策略。策略 + 复合可能会奏效。
-
可能是visitor?
-
@Eineki 不是更适合遍历数据结构的访问者吗?
-
我真的没有看到装饰器有什么问题。当然,如果您向 S 添加一个新函数,则必须为装饰器实现转发逻辑会带来一些不便,但这种情况很少见(如果您忘记了它,您会遇到编译错误)。我不正确理解我担心的第二点。如果您想要在收敛函数的某些实现中使用不同的装饰器,您可以执行
interface Decorator extends S并在其中声明必要的收敛函数。