抽象メソッドまたは仮想メソッドを使用する必要がありますか?


11

基本クラスが純粋なインターフェイスクラスであることが望ましくないと仮定し、以下の2つの例を使用すると、抽象メソッドまたは仮想メソッドクラス定義を使用する方が適切なアプローチですか?

  • 「抽象」バージョンの利点は、見た目がきれいで、派生クラスに意味のある実装を強制的に提供させることです。

  • 「仮想」バージョンの利点は、他のモジュールによって簡単に引き込められ、抽象バージョンが必要とするような基本的なフレームワークを追加することなくテストに使用できることです。

要約版:

public abstract class AbstractVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

仮想バージョン:

public class VirtualVersion
{
    public virtual ReturnType Method1()
    {
        return ReturnType.NotImplemented;
    }

    public virtual ReturnType Method2()
    {
        return ReturnType.NotImplemented;
    }
             .
             .
    public virtual ReturnType MethodN()
    {
        return ReturnType.NotImplemented;
    }

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

インターフェースが望ましくないと仮定するのはなぜですか?
アンソニーペグラム

部分的に問題がなければ、一方が他方より優れていると言うことは困難です。
コーディズム

@Anthony:このクラスに入る有用な機能があるため、インターフェースは望ましくありません。
ダンク

4
return ReturnType.NotImplemented?マジ?コンパイル時に未実装の型を拒否できない場合(できます。抽象メソッドを使用)、少なくとも例外をスローします。
ジャン・ヒューデック

3
@Dunk:ここで彼らはある必要。戻り値チェックされません。
ヤンHudec

回答:


15

私の投票は、私があなたのものを消費していた場合、抽象メソッドに対するものです。それは「早く失敗する」ことと同じです。すべてのメソッドを追加するのは宣言時に苦痛になるかもしれません(ただし、適切なリファクタリングツールはすぐにこれを行います)が、少なくとも問題が何であるかをすぐに知って修正します。6か月と12人分の変更を後でデバッグして、実装されていない例外が突然発生する理由を確認したいのです。


NotImplementedエラーを予期せずに取得することに関する良い点。実行時エラーの代わりにコンパイル時エラーが発生するため、これは抽象的側面でプラスです。
ダンク

3
+1-早い段階での失敗に加えて、継承者は、自分が十分だと思ったものを実行してから実行時に失敗するのではなく、「Implement Abstract Class」で実装する必要があるメソッドをすぐに確認できます。
テラスティン

コンパイル時に失敗するとプラスになり、IDEを使用してメソッドを迅速に自動実装することを参照したため、この回答を受け入れました。
ダンク

25

仮想バージョンは、バグが発生しやすく、セマンティック的に正しくありません。

Abstractは、「このメソッドはここでは実装されていません。このクラスを機能させるには実装する必要があります」と言っています。

Virtualは「デフォルトの実装を持っていますが、必要に応じて変更できます」と言っています

最終的な目的がテスト容易性である場合、通常はインターフェイスが最適なオプションです。(このクラスはaxではなくxを実行します)。ただし、これをうまく機能させるには、クラスを小さなコンポーネントに分割する必要があるかもしれません。


3

これは、クラスの使用法に依存します。

メソッドに合理的な「空の」実装がある場合、多くのメソッドがあり、多くの場合、それらのいくつかだけをオーバーライドしますvirtual。その後、メソッドを使用することは理にかなっています。たとえば、ExpressionVisitorこの方法で実装されます。

それ以外の場合は、abstractメソッドを使用する必要があると思います。

理想的には、実装されていないメソッドを使用すべきではありませんが、場合によってはそれが最良のアプローチです。しかし、それを行うことにした場合、そのようなメソッドはNotImplementedException特別な値を返さずにスローする必要があります。


「NotImplementedException」はしばしば省略のエラーを示し、「NotSupportedException」は明白な選択を示すことに注意してください。それ以外は、同意します。
アンソニーペグラム

多くのメソッドは、「Xに関連する義務を果たすために必要なことは何でも行う」という用語で定義されていることに注意する価値があります。Xに関連するアイテムを持たないオブジェクトでこのようなメソッドを呼び出すことは無意味かもしれませんが、それでも明確に定義されます。さらに、オブジェクトにXに関連する義務がある場合とない場合、最初に義務があるかどうかを尋ね、条件付きで満たすように依頼するよりも、「Xに関連するすべての義務を満たしている」と無条件に言う方が一般的にクリーンで効率的です。
supercat

1

基本クラスが実装する別のインターフェイスを定義することを再検討し、抽象的なアプローチに従うことをお勧めします。

このようなイメージングコード:

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public abstract class AbstractVersion : IVersion
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

これにより、次の問題が解決します。

  1. AbstractVersionから派生したオブジェクトを使用するすべてのコードを実装して、代わりにIVersionインターフェイスを受け取るように実装できるようになりました。つまり、ユニットテストをより簡単に行えるようになりました。

  2. 製品のリリース2では、インターフェイスIVersion2を実装して、既存の顧客コードを壊すことなく追加機能を提供できます。

例えば。

public interface IVersion
{
    ReturnType Method1();        
    ReturnType Method2();
             .
             .
    ReturnType MethodN();
}

public interface IVersion2
{
    ReturnType Method2_1();
}

public abstract class AbstractVersion : IVersion, IVersion2
{
    public abstract ReturnType Method1();        
    public abstract ReturnType Method2();
             .
             .
    public abstract ReturnType MethodN();
    public abstract ReturnType Method2_1();

    //////////////////////////////////////////////
    // Other class implementation stuff is here
    //////////////////////////////////////////////
}

また、依存関係の逆転についても読む価値があります。このクラスに、効果的な単体テストを妨げるハードコーディングされた依存関係が含まれないようにするためです。


異なるバージョンを処理する機能を提供することに賛成しました。ただし、通常の設計の一部として、インターフェイスクラスを効果的に使用することを試み、再試行しましたが、インターフェイスクラスが価値をまったく/ほとんど提供せず、実際にコードをわかりにくくするのではなく、実際にコードを難読化していることに常に気付きます。インターフェイスクラスのように、複数のクラスを別のクラスから継承することは非常にまれであり、共有できないかなりの量の共通性はありません。抽象クラスは、インターフェイスが提供しない組み込み可能な共有可能な側面を取得するため、より適切に動作する傾向があります。
ダンク14年

また、適切なクラス名で継承を使用すると、全体として精神的に結びつけるのが困難な一連の機能的なインターフェイスクラス名よりもはるかに直感的にクラス(およびシステム)を理解することができます。また、インターフェイスクラスを使用すると、大量の余分なクラスが作成される傾向があり、システムをさらに別の方法で理解しにくくします。
ダンク14年

-2

依存性注入はインターフェイスに依存しています。簡単な例を示します。クラスStudentにはCreateStudentという関数があり、インターフェイス「IReporting」を実装するパラメーターが必要です(ReportActionメソッドを使用)。学生を作成した後、具象クラスパラメータでReportActionを呼び出します。学生を作成した後に電子メールを送信するようにシステムが設定されている場合、ReportAction実装で電子メールを送信する具象クラスで送信するか、ReportAction実装でプリンタに出力を送信する別の具象クラスで送信できます。コードの再利用に最適です。


1
これは、前の5つの回答で作成され説明されたポイントに対して実質的なものを提供していないようです
-gnat
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.