継承が悪用される可能性がある場合を確認するための簡単なヒューリスティックを探す際に、次の仮説を立てました。
サブクラス
B
がメソッドfoo
をオーバーライドするが、base.foo()
を呼び出さない場合、同じメソッドシグネチャのみを使用するため、そのメソッドの継承は壊れているようですが、必ずしも同じ動作です。
これは、本来あるべきではない継承を特定する正しい方法ですか?
意図せずに継承を誤ってしまう方法は100万通りあります。サブクラスが適切に動作することを確認するために確認できることの完全なリストはありません。たとえあったとしても、サブクラスにそれを強制することはできません。そして、可能であれば、スーパークラスは、一部のサブクラスを意図せずに壊すような方法で変更することもできます。
よりよい質問は、どのようにavoid問題を解決するかであり、以下の回答があります。
これらのルールがあっても、平等のような問題がまだあります。
Robert Brautiganの回答は適切であり、私はそれを置き換えるつもりはありませんが、投稿した理由について直接フィードバックを追加したいと思いました。
サブクラスBがメソッドfooをオーバーライドするが、base.foo()を呼び出さない場合、そのメソッドの継承は壊れているようです
その場合、base.foo()
が呼び出されるように強制するのではなく、コンパイラーが(out
を設定する必要があることを強制できるのと同様に)意味がありません。メソッド本体のどこかにパラメータ)。
base.foo()
を呼び出すかどうかは、派生クラスが実装を変更するか実装するか(呼び出さないで)か、さらに拡張するか(呼び出すか)によって異なります。あなたの派生クラスは何か別のことをしていますか、それとも何か特別なことをしていますか?
これらのオプションはどちらも、継承の有効なユースケースになります。あなたが思いついた仮説は、それが動作を拡張することのみを考慮し、それを上書きすることはしないという点で、非常に厳格です。 override
キーワードの命名に関するヒントを参考にしてください-オーバーライド動作は、確かに継承のユースケースとして有効です。
同じメソッドシグネチャのみを使用しますが、必ずしも同じ動作ではありません。
あなたの仮説は間違っていますが、私はあなたがどこから来ているのかを理解しています。根本的なアイデアにはいくつかのメリットがありますが、そこから導き出した結論は違います。
はい、動作はfunctionalレベルで同じである必要があります(つまり、メソッドの目的)。基本メソッドがメッセージをログに記録し、オーバーライドされたメソッドがデータベーステーブルを削除する場合、それらは2つのまったく異なる動作であるため、オーバーライドの合理的な実装ではありません。
ただし、これは、オーバーライドされたメソッドがtechnicalレベル(つまり、コード自体)で異なることができないという意味ではありません。派生メソッドがこのような異なるアプローチを使用します(同じ意図で!)基本メソッドを実際に再利用することはできません。
これまでに作成したクラスのいずれかでオーバーライドするたびに基本メソッドを呼び出したことがない場合は、継承を実装するときにおそらく何かが間違っています。しかし、これは、ベースメソッドを一度も呼び出さないことが、その特定のインスタンスにおける継承の乱用の直接的な証拠であることを意味するものではありません。
あなたが思いついた仮説にあなたを導くものを考えると、あなたがしたい状況にいると思いますguarantee基本ロジックが呼び出され、派生クラスが迂回できないそれ。
これは、基本クラスの適切な構造で実施できます。例:
_public class BasePizza
{
sealed public void MakePizza()
{
MakePizzaBase();
AddToppings();
}
private void MakePizzaBase()
{
// this will ALWAYS be done and derived classes cannot avoid it
Console.Writeline("Making dough");
Console.Writeline("Adding pizza sauce");
}
protected virtual void AddToppings()
{
// Derived classes CAN rewrite this if they want
Console.WriteLine("Adding cheese");
}
}
_
あなたが作るすべての派生ピザは常に生地とピザソースを持っていますが、彼らが望むなら、彼らはデフォルトのトッピング(この場合はチーズ)を追加することを避けて自由です。例えば:
_public class LactoseFreePizza : BasePizza // Basic pizza with extra meat but NO cheese!
{
protected override void AddToppings()
{
// No cheese on this!
Console.WriteLine("Adding lots of meat");
}
}
public class MeatLoversPizza : BasePizza // Basic pizza with extra meat
{
protected override void AddToppings()
{
// Here, we do use the default toppings
base.AddToppings();
Console.WriteLine("Adding lots of meat");
}
}
_
これらのメソッドがどのようにプライベートで保護されているかに注目してください。唯一のパブリックメソッドはMakePizza()
であるため、消費者はピザを作成するためにそのメソッドを経由する必要があります。これは、基本クラスが一部のロジックが常に実行されることを強制できることを意味します。これはいくつかの異なることに依存しています:
MakePizzaBase
はprivate
なので、派生クラスはアクセスしたり変更したりできません。MakePizza
も派生クラスでは変更できませんが、コンシューマがアクセスする必要があるため、public
にする必要があります。したがって、MakePizza
はsealed
であるため、派生クラスはその動作を変更することだけを決定することはできません。AddToppings
はvirtual
メソッドです。これは、派生クラスが変更できる部分(MakePizza
ロジックの)であるためです彼らが合うと思うように。ただし、コンシューマーがこのメソッドを直接呼び出すことは許可されていないため(MakePizza
を使用して、基本ロジックが確実に実行されるようにするため)、AddToppings
メソッドをprotected
。AddToppings
をabstract
メソッドにすることも可能であったことに注意してください。つまり、デフォルトの動作はありませんが、派生クラスに追加するトッピングを明示的に定義するように強制します。空のメソッドを実装することもできますが、強制的に実装されます。これは、一部のケースでは理にかなっていますが、すべてのケースではありません。これはあなたが望むものを手に入れます。基本クラスは、派生クラスが何をしようとしても、そのロジックの一部が常に実行されるように強制できます。
注sealed
は、この例では実際にはオプションです。virtual
でないメソッドは、本質的にsealed
と見なされるためです。ただし、3番目の継承レイヤーがあり、AとBの間でこのメソッドを継承したいが、B(C、D、またはE)の派生に対してではない場合、Aはそのメソッドをvirtual
として定義し、 Bはそれを継承すると同時にsealed
にして、それ以上の派生で上書きされないようにします。
この場合、デフォルトでsealed
を追加することをお勧めします。意図を電信で伝えるため、このメソッドが開発者に通知されるためです必須ではない将来の任意の時点で仮想化されます。
注
だからといって、継承が常に正しいアプローチであるとは限りません。継承が適切なツールではない場合がたくさんあります。この回答では、具体的なコンテキストを指定しなかったため、特定のユースケースに継承が適切かどうかを評価することはしません。howについてのみ言及し、継承がここで保証されます。