抽象クラス内のすべてのパブリックメソッドを仮想としてマークする必要がありますか?


12

最近、使用しているOSSの抽象基本クラスを更新して、仮想化することでテストしやすくしました(2つを組み合わせたインターフェイスを使用できませんでした)。これにより、仮想に必要なすべてのメソッドをマークするか、すべてのパブリックメソッド/プロパティを仮想にマークする必要があるかを考えるようになりました。私は一般的に、すべての方法を仮想化する必要があるというロイ・オセロベ同意しますが、これが必要かどうかについて考えさせられるこの記事出会いました。ただし、簡単にするためにこれを抽象クラスに限定します(具体的なパブリックメソッドがすべて仮想であるべきかどうかは、特に議論の余地があります)。

サブクラスがメソッドを使用できるようにするが、実装をオーバーライドしたくない場所を確認できました。しかし、リスコフの代替原則に従うことを信頼している限り、オーバーライドすることを許可しないのはなぜですか?抽象としてマークすることで、とにかく特定のオーバーライドを強制しているので、抽象クラス内のすべてのパブリックメソッドは実際に仮想としてマークする必要があるように思えます。

しかし、私が考えていないかもしれない何かがある場合に備えて尋ねたいと思いました。抽象クラス内のすべてのパブリックメソッドを仮想化する必要がありますか?


これが役立つ場合があります:stackoverflow.com/questions/391483/…–
NoChance

1
たぶん、C#ではデフォルトが仮想ではなく、Javaではデフォルトである理由を調べる必要があります。理由はおそらく、答えに対する洞察を与えてくれるでしょう。
ダニエルリトル

回答:


9

しかし、リスコフの代替原則に従うことを信頼している限り、オーバーライドすることを許可しないのはなぜですか?

たとえば、アルゴリズムのスケルトン実装を修正し、特定の部分のみをサブクラスによって(再)定義できるようにするためです。これは、テンプレートメソッドパターンとして広く知られています(以下で強調します)。

したがって、テンプレートメソッドは、タスクセマンティクスの全体像と、メソッドの選択とシーケンスのより洗練された実装の詳細を管理します。この大きな画像は、手元のタスクの抽象メソッドと非抽象メソッドを呼び出します。非抽象メソッドはテンプレートメソッドによって完全に制御されますが、サブクラスに実装された抽象メソッドは、パターンの表現力と自由度を提供します。一部またはすべての抽象メソッドはサブクラスに特化することができ、サブクラスの作成者は、より大きなセマンティクスへの最小限の変更で特定の動作を提供できます。テンプレートメソッド(非抽象)はこのパターンでは変更されず、下位の非抽象メソッドと抽象メソッドが元々意図された順序で呼び出されるようにします。

更新

私が取り組んでいるプロジェクトの具体例:

  1. さまざまな「画面」を介してレガシーメインフレームシステムと通信します。各画面には、特定のデータビットを含む固定名、位置、長さのフィールドがあります。要求は特定のフィールドを特定のデータで満たします。応答は、1つ以上の他のフィールドにデータを返します。各トランザクションは同じ基本ロジックに従いますが、詳細は画面ごとに異なります。サブクラスで画面固有の詳細を定義できるようにしながら、いくつかの異なるプロジェクトでテンプレートメソッドを使用して、画面処理ロジックの固定スケルトンを実装しました。
  2. DBテーブルの構成データをExcelファイルとの間でエクスポート/インポートします。繰り返しますが、Excelファイルの処理とDBレコードの挿入/更新、またはExcelへのレコードのダンプの基本スキーマは各テーブルで同じですが、各テーブルの詳細は異なります。そのため、テンプレートメソッドは、コードの重複を排除し、コードの理解と保守を容易にする非常に明白な選択肢です。
  3. PDFドキュメントの生成。各ドキュメントの構造は同じですが、多くの要因に応じて、その内容は毎回異なります。繰り返しますが、テンプレートメソッドを使用すると、生成アルゴリズムの固定スケルトンを、ケース固有の変更可能な詳細から簡単に分離できます。実際には。文書は複数のセクションで構成されており、各セクションは0個以上のフィールドで構成されているため、ここでは複数のレベルにも適用されます。ここでは、テンプレートメソッドが3つの異なるレベルに適用されます。

最初の2つのケースでは、元のレガシー実装はStrategyを使用していましたが、結果として多くの重複したコードが発生しましたが、もちろん長年にわたって微妙な違いが生じ、多くの重複したまたはわずかに異なるバグが含まれていて、保守が非常に困難でした。テンプレートメソッドへのリファクタリング(およびJavaアノテーションの使用など、その他の機能強化)により、コードサイズが約40〜70%削減されました。

これらは、私の頭に浮かぶ最新の例にすぎません。これまで取り組んできたほぼすべてのプロジェクトから、より多くの事例を引用することができました。


GoFを引用するだけでは答えになりません。そのようなことをする実際の理由を与える必要があります。
DeadMG

@DeadMG、私はキャリアの間に定期的にテンプレートメソッドを使用しているので、それが非常に実用的で有用なパターンであることは明らかだと思いました(GoFパターンのほとんどは...これらは理論的な学術的な演習ではなく、収集されたものです実世界の経験から)。しかし、明らかに誰もがそれに同意するわけではありません...私は私の答えにいくつかの具体例を追加します。
ペテルトレック

2

完全に合理的であり、抽象ベースクラスに非仮想メソッドを含めることが望ましい場合があります。それが抽象クラスだからといって、必ずしもそのすべての部分が多態的に使用可能であるとは限りません。

たとえば、「非仮想ポリモーフィズム」イディオムを使用すると、仮想関数が呼び出される前に特定の前提条件または事後条件が満たされることを保証するために、関数が非仮想メンバー関数から多態的に呼び出されます。

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};

私は、全体的な動作が同じである限り、これを仮想化できると主張します。別の実装(データベースとメモリ内)を使用して、事前/事後条件を埋めることができます。それ以外の場合、プライベート関数である必要がありますか?
ジャスティンピホニー

2

クラスが抽象になるには、クラスに1つの仮想メソッドを含めるだけで十分です。使用する予定のポリモーフィズムのタイプに応じて、どのメソッドを仮想化し、どのメソッドを仮想化しないかに注意を払う必要があります。


1

抽象クラスの非仮想メソッドの使用法を自問してください。そのようなメソッドは、それを有効にするための実装が必要になります。しかし、クラスに実装がある場合、抽象クラスと呼ぶことはできますか?言語/コンパイラで許可されていても、意味がありますか?個人的にはそうは思いません。子孫が実装することが期待される抽象メソッドを持つ通常のクラスがあります。

私の第一言語はC#ではなくDelphiです。Delphiでは、メソッドの抽象にマークを付けると、仮想にマークする必要があります。そうしないと、コンパイラがエラーを出します。最新の言語の変更をあまり厳密にフォローしていませんが、抽象クラスがDelphiにある場合、またはデルファイに来る場合、コンパイラが非仮想メソッド、プライベートメソッド、および抽象とマークされたクラスのメソッド実装について文句を言うことを期待しますクラスレベルで。


2
私は、いくつかの抽象メソッド、いくつかの仮想メソッド、およびいくつかの非仮想メソッドを持つ抽象クラスを持つことは完全に理にかなっていると思います。私はそれが常にあなたが何をしたいのか正確に依存していると思います。
-svick

3
まず、C#qについて説明します。C#の抽象メソッドは暗黙的に仮想であり、実装することはできません。最初の点として、C#の抽象クラスは、何らかの実装を行うことができ、また実装する必要があります(そうでない場合は、単にインターフェイスを使用する必要があります)。クラスが抽象的であるという点は、サブクラス化する必要があるということですが、(理論的には)すべてのサブクラスが使用するロジックが含まれています。これにより、コードの重複が削減されます。私が尋ねているのは、それらの実装のいずれかがオーバーライドされることから閉鎖されるべきかどうかです(基本的な方法が唯一の方法であると言っています)。
ジャスティンピホニー

@JustinPihony:ありがとう。Delphiでは、メソッド抽象をマークすると、その実装を提供するとコンパイラからエラーが発生します。異なる言語がどのように概念を異なる方法で実装し、それによりユーザーに異なる期待をもたらすか興味深い。
マルジャンヴェネマ

@svick:はい、それは完全に理にかなっています、私はそれを抽象クラスとは呼ばないでしょうが、抽象メソッドを持つクラス...
マルジャンヴェネマ

@MarjanVenema、しかしそれはC#が使用する用語ではありません。クラス内のメソッドをマークする場合は、クラスabstract全体もマークする必要がありますabstract
svick

0

しかし、リスコフの代替原則が守られることを信頼している限り、それをオーバーライドすることを許可しないのはなぜですか?

これが事実だとは思わないので、特定のメソッドを仮想化しません。さらに、特定のメソッドを仮想化しないことにより、どのメソッドを実装する必要があるかを継承者に通知しています。

個人的には、仮想ではなく利便性のために存在するメソッドのオーバーロードを作成することが多いため、クラスのユーザーは一貫したデフォルトを持つことができ、実装者はその暗黙の動作を破るエラーすらできません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.