2011-04-29 66 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 
    } 
} 

讓事情簡單一些,而且不要試圖使它不可能或防呆。編碼防禦(一種很好的做法)和編碼對於愚蠢的(徒勞無益的努力)之間存在差異。簡單地說明如何對驗證器進行子類化的編碼規則。也就是說,把責任放在執行者身上。如果他們不能遵循指導原則,那麼防禦性編碼就不會保護你的系統免受他們的愚蠢。埃爾戈,保持清楚和簡單的事情。