我遇到的情况是我要扩展给定类的功能,但我不确定最好的方法。我开始调用“向上”功能,现在切换到“向下”,但我看到两者都有问题。让我解释一下我的意思。首先,“向上”的方法:
public class ParentValidator
{
public void validate() {
// Some code
}
}
public class ChildValidator extends ParentValidator
{
@Override
public void validate() {
super.validate();
// Some code
}
}
public class GrandchildValidator extends ChildValidator
{
@Override
public void validate() {
super.validate();
// Some code
}
}
这个功能非常好,但它要求我总是记得在我的方法体中放置super.validate()或者不会执行父类中的逻辑。此外,由于子类实际上可以替换/修改父类中定义的代码,因此以这种方式扩展可以被认为是“不安全的”。这就是我称之为“向上”调用方法的原因,因为我正在调用更高级别的方法。
为了解决这些不足,我决定将ParentValidator.validate()设为final,并让它调用另一种方法。以下是我的代码修改为:
public class ParentValidator
{
public final void validate() {
// Some code
subValidate();
}
protected void subValidate() {}
}
public class ChildValidator extends ParentValidator
{
@Override
public final void subValidate() {
// Some code
subSubValidate();
}
protected void subSubValidate() {}
}
public class GrandchildValidator extends ChildValidator
{
@Override
public void subSubBalidate() {
// Some code
subSubSubValidate();
}
protected void subSubSubValidate();
}
当我说我正在向下调用时,这就是我所指的,因为每个类都调用继承链“向下”类的方法。
使用这种方法,我可以保证将执行父类中的逻辑,我喜欢。但是,它不能很好地扩展。我拥有的层次越多,它就越丑陋。在某种程度上,我觉得这很优雅。在两个层面上,它开始变得粗制滥造。在三个或更多,它是可怕的。
另外,就像我必须记得调用super.validate()作为我孩子的任何验证方法的第一行一样,我现在必须记住在我的任何一个结尾处调用一些“subValidate”方法。父母的验证方法,所以似乎没有变得更好。
有没有更好的方法来进行我甚至没有涉及的此类扩展。这两种方法都有一些严重的缺陷,我想知道我是否有更好的设计模式。
答案 0 :(得分:4)
在你所描述的第一种方法中,你使用的是简单继承,你的第二种方法更接近Gang of Four [GoF]所谓的Template Method Pattern,因为你的父类正在使用所谓的Hollywood Principle:“不要打电话给我们,我们会打电话给你”。
但是,您可以从父类中将subvalidate()方法声明为抽象中受益,并且通过此方法,确保所有子类都被强制实现它。那么这将是一个真正的模板方法。
public abstract class ParentValidator
{
public final void validate() {
//some code
subValidate();
}
protected abstract void subValidate() {}
}
根据您的操作,还有其他模式可以帮助您以不同的方式执行此操作。例如,您可以使用Strategy Pattern来执行验证,并使用此优势组合而不是继承,如前所述,但结果是您需要更多验证类。
public abstract class ParentValidator
{
private final ValidatorStrategy validator;
protected ParentValidator(ValidatorStrategy validator){
this.validator = validator;
}
public final void validate() {
//some code
this.validator.validate();
}
}
然后,您可以为您拥有的每种类型的验证器提供特定的验证策略。
如果你想充分利用这两个世界,你可能会考虑将解决方案作为Decorator Pattern来实现,其中子类可以扩展父类的功能,并且仍然坚持使用公共接口。
public abstract class ValidatorDecorator implements Validator
{
private final Validator validator;
protected ParentValidator(Validator validator){
this.validator = validator;
}
public final void validate() {
//some code
super.validate(); //still forced to invoke super
this.validator.validate();
}
}
所有模式都有后果,优点和缺点,您必须仔细考虑。
答案 1 :(得分:2)
我更喜欢1)针对接口的程序,以及2)选择合成而不是继承。这就是我的表现。有些人喜欢它,有些人不喜欢。它有效。
// java pseudocode below, you'll need to work the wrinkles out
/**
* Defines a rule or set of rules under which a instance of T
* is deemed valid or invalid
**/
public interface ValidationRule<T>
{
/**
* @return String describing invalidation condition, or null
* (indicating then that parameter t is valid */
**/
String apply(final T t);
}
/**
* Utility class for enforcing a logical conjunction
* of zero or more validatoin rules on an object.
**/
public final class ValidatorEvaluator
{
/**
* evaluates zero or more validation rules (as a logical
* 'AND') on an instance of type T.
**/
static <T> String apply(final T t, ValidationRule<T> ... rules)
{
for(final ValidationRules<T> v : rules)
{
String msg = v.apply(t);
if( msg != null )
{
return msg; // t is not valid
}
}
return null;
}
}
// arbitrary dummy class that we will test for
// i being a positive number greater than zero
public class MyFoo
{
int i;
public MyFoo(int n){ i = n; }
///
}
public class NonZeroValidatorRule implements ValidatorRule<MyFoo>
{
public String apply(final MyFoo foo)
{
return foo.i == 0 ? "foo.i is zero!" : null;
}
}
// test for being positive using NonZeroValidatorRule and an anonymous
// validator that tests for negatives
String msg = ValidatorEvaluator.apply( new MyFoo(1),
new NonZeroValidatorRule(),
new ValidatorRule<MyFoo>()
{
public String apply(final MyFoo foo)
{
return foo.i < 0 ? "foo.i is negative!" : null;
}
}
);
if( msg == null )
{
\\ yay!
...
}
else
{
\\ nay...
someLogThingie.log("error: myFoo now workie. reason=" + msg );
}
可以通过这种方式实施更复杂,非平凡的评估规则。
这里的关键是除非存在is-a关系,否则不应使用继承。不要仅用于回收或封装逻辑。如果您仍然觉得需要使用继承,那么请不要试图确保每个子类都执行从超类继承的验证逻辑。每个子类的实现都在super上执行显式执行:
public class ParentValidator
{
public void validate() { // notice that I removed the final you originally had
// Some code
}
}
pubic class ChildValidator extends ParentValidator
{
@Override
public void validate() {
// Some code
super.validate(); // explicit call to inherited validate
// more validation code
}
}
保持简单,不要试图让它变得不可能或万无一失。编码防御性(良好做法)和编码愚蠢之间存在差异(徒劳无功)。简单地列出了如何对验证器进行子类化的编码规则。也就是说,把责任放在实现者身上。如果他们不遵守指南,那么任何数量的防御性编码都不会保护您的系统免受其愚蠢的影响。因此,保持清晰和简单。
答案 2 :(得分:1)
如果你的subSubSubValidate是相关的一般功能,我更喜欢使用组合而不是继承。您可以提取新类并将其移动到那里,而不是在其他类中继承而不使用它。
还有
“赞成'对象组成'结束 '阶级继承'。“(四人帮 1995:20)
答案 3 :(得分:0)
或许查看访客模式可以帮助您开发模式。