2011-04-29 9 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()を最終的にして別のメソッドを呼び出させることにしました。ここに私のコードは、に修正されたものです。これは私が、私は各クラスが継承チェーン「ダウン」のクラスにメソッドを呼び出すと、私は下向きに呼んでいることを言うときに言及されたものである

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(); 
} 

この方法を使用すると、親クラスのロジックが実行されることが保証されますが、これは私が好きです。しかし、それはうまく拡張できません。私が持っているより多くの相続層、それは醜いです。あるレベルでは、これはとてもエレガントだと思います。 2つのレベルで、それは不毛に見えるようになります。 3つ以上では、それは恐ろしいです。

さらに、私の子供の検証メソッドの最初の行としてsuper.validate()を呼び出すのと同じように、私は今、いくつかの "subValidate"メソッドを呼び出す必要があります。私の親の検証方法は、それほど良くはないようです。

私が触れていないこのタイプの拡張を行うには、より良い方法がありますか?これらのアプローチのどちらかに重大な欠陥があり、私が使用できるより良いデザインパターンがあるかどうかは疑問です。

答えて

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

あなたのsubSubSubValidateが一般的な機能に関連している場合、継承を超えてコンポジションを使用することをお勧めします。新しいクラスを抽出し、他のクラスで継承することなく使用できるよりも、そこに移動することができます。

" 'クラスの継承' の上に好意 'オブジェクト組成物は、'。" もあります(ギャングオブフォー 1995:20)

0

おそらくあなたのパターンを開発するのに役立つ可能性があります。ここで

はそれにいくつかの情報です:あなたは、単純な継承を使用しているあなたの最初のアプローチとして記述するものでhttp://en.wikipedia.org/wiki/Visitor_pattern

+0

このアプローチの唯一の制限は、訪問しているオブジェクトに対してコールバックを行う必要があることです。オブジェクトが「閉じている」場合は、反射(醜い)に解決する必要があります。 –

2

私は、1)インターフェースに対してプログラムを行い、2)継承を超える合成を選択することをお勧めします。これはどういう意味ですかIしました。それを好む人もいるし、そうでない人もいる。できます。

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

は、物事をシンプルに保つ、それが不可能またはばかプルーフをしようとしないでください。防御的にコードを書くこと(良い方法)との愚かなコードとの間には違いがあります(無駄な努力)。バリデーターをサブクラス化する方法に関するコーディング規則を単にレイアウトしてください。つまり、実装者に負担をかけます。彼らがガイドラインに従うことができない場合、防御コーディングの量はあなたのシステムをその愚かさから守りません。エルゴ、物事をはっきりとシンプルに保つ。

関連する問題