2011-04-29 101 views
9

我遇到了一种情况,我要扩展给定类的功能,但我不确定最好的解决方法。我开始通过调用功能“向上”,现在已经切换到“向下”,但我发现两者都有问题。让我解释我的意思。首先,“向上”的方法:什么是扩展功能的最佳途径?

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()在我的方法体或在父类(ES)的逻辑赢得”被执行。另外,由于子类可以实际替换/修改父类中定义的代码,所以这种方式的扩展可以被认为是“不安全的”。这就是我称之为“向上”的方法,因为我正在调用更高级别的方法。

为了应对这些不足,我决定制作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”方法我的父母的验证方法,所以似乎没有得到任何改善。

有没有更好的方法来做这种类型的扩展,我甚至没有涉及到。这些方法都有一些严重的缺陷,我想知道是否有更好的设计模式可以使用。

回答

4

在你形容为你的第一种方法使用的是简单的继承,你的第二个方法是更接近什么Gang of Four [四人帮]称为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

如果您的subSubSubValidate是相关的一般功能,我宁愿使用组合继承。你可以提取新的类并将它移到那里,而不用在其他类中使用继承。

还有

“青睐 '对象组合物' 过度 '类继承'。” (四人帮 1995:20)

0

也许看看访客模式可能会帮助你发展你的模式。

下面是一些关于它的信息:http://en.wikipedia.org/wiki/Visitor_pattern

+0

这种方法的唯一限制是您必须对被访问的对象进行回调。如果物体“闭合”,那么你必须解决反射(丑陋)。 – 2011-04-29 15:15:24

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关系,否则不应该使用继承。不要只用它来回收或封装逻辑。如果你仍然觉得你需要使用继承,那么不要过分地试图确保每个子类都执行从超类继承的验证逻辑。每个子类都实现做超明确执行:

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

让事情简单一些,而且不要试图使它不可能或防呆。编码防御(一种很好的做法)和编码对于愚蠢的(徒劳无益的努力)之间存在差异。简单地说明如何对验证器进行子类化的编码规则。也就是说,把责任放在执行者身上。如果他们不能遵循指导原则,那么防御性编码就不会保护你的系统免受他们的愚蠢。埃尔戈,保持清楚和简单的事情。