web-dev-qa-db-ja.com

基本クラスと抽象メソッドの空の仮想メソッド

特定のケースに限定されない質問を見つけることができなかったので、これを非常に一般的にするようにします。

たとえば、一連のドキュメントに対するエクストラクタ基本クラスが必要です。各ドキュメントには固有のプロパティがありますが、最終的にはドキュメントです。したがって、それらすべてに共通の抽出操作を提供したいと考えています。

これらはすべてドキュメントですが、前述のとおり、多少異なります。一部のプロパティがある場合とない場合があります。

Document基本抽象クラスと、それから継承するFancyDocumentおよびNotSoFancyDocumentクラスがあるとします。 FancyDocumentにはSectionAがありますが、NotSoFancyDocumentにはありません。

そうは言っても、これを実装する最良の方法として何を守りますか? 2つのオプションは次のとおりです。

  • 基本クラスの空の仮想メソッド

基本クラスで空の仮想メソッドを使用すると、プログラマーは、異なるタイプのドキュメントに意味のあるメソッドのみをオーバーライドできます。次に、次のように、メソッドのdefaultを返す抽象基本クラスにデフォルトの動作を設定します。

public abstract class Document
{
    public virtual SectionA GetDocumentSectionA()
    {
        return default(SectionA);
    }
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation            
    }
}

public class NotSoFancyDocument : Document
{
    // Does not implement method GetDocumentSectionA because it doesn't have a SectionA
}
  • 空の具体的なメソッドまたはNotImplementedExceptionをスローする具体的なメソッド

NotSoFancyDocument ではないSectionAがありますが、他の人が持っている場合は、単に default その中のメソッド、またはNotImplementedExceptionをスローすることができます。それは、プログラムがどのように作成されたかなどに依存します。私たちはこのようなものを思いつくことができます:

//// Return the default value

public abstract class Document
{
    public abstract SectionA GetDocumentSectionA();
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation
    }
}

public class NotSoFancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        return default(SectionA);
    }
}

OR

//// Throw an exception

public abstract class Document
{
    public abstract SectionA GetDocumentSectionA();
}

public class FancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        // Specific implementation
    }
}

public class NotSoFancyDocument : Document
{
    public override SectionA GetDocumentSectionA()
    {
        throw new NotImplementedException("NotSoFancyDocument does not have a section A");
    }
}

個人的には、抽象メソッドアプローチの方が優れていると思います。「ねえ、SectionAをドキュメントにできるようにする必要があります。どうでもいいのです。」一方、仮想メソッドは「ねえ、このセクションAはここにあります。それが十分でない場合は、取得方法を自由に変更してください。」という意味です。

最初の問題は、オブジェクト指向プログラミングの匂いの兆候だと思います。

これについてのあなたの意見は何ですか?

5
Smur

この場合、基本クラスはSectionAについて何も認識していません。派生クラスは、その型に必要な追加のプロパティを実装する必要があります。

ドキュメントのタイプに関係なく別のクラスが情報を引き出す必要がある特定の操作では、基本クラスのメソッドを理想的には基本的な実装で仮想化し、派生クラスが必要に応じてそれをオーバーライドできるようにします(例ToPlainText which Documentにあるドキュメントのすべてのセクションを出力するだけで、FancyDocumentは実装をオーバーライドしてSectionAも出力することができます)。

別のクラスがドキュメントのタイプを気にしないが、特定のプロパティがある場合はインターフェイスを使用するインスタンスの場合。 IDocumentにはすべての共通セクションがあり、Documentはそれを実装します。 IDocumentWithSectionAIDocumentを継承し、FancyDocumentはそれを実装します。これにより、NeitherFancyNorNotFancyDocumentも実装できるSectionAを持つ別のIDocumentsWithSectionAを導出できます。

明らかにあなたはIDocumentWithSectionAよりも有用な名前を持っているでしょうが、それはユースケースに依存します。

TL; DRすべてのドキュメントに共通するものといくつかの共通機能に抽象クラスを使用し、ドキュメントが何を持っているかを示すコントラクトとしてインターフェイスを使用します。

4
Chao

コメントで述べたように、これらのオプションはどちらも非常に良いものではありません。それがおそらくあなたもこの質問をしている理由です。

空の仮想メソッドを使用したソリューションは、特定のドキュメントインスタンスによってサポートされているものに関する情報を提供しません。さらに、すべての可能なメソッドを基本クラスに追加する必要があります。これにより、基本クラスに追加のメソッドが必要であると判断した場合、すべてのクライアントが強制的に更新されます。

例外をスローするという解決策は、ドキュメントクラスのクライアントがすべてのメソッドで発生する可能性のある例外を処理する必要があるため、さらに悪い可能性があります。それは恐ろしくて信頼できないコードにつながります。


苦労している問題は、ドキュメントクラスが目的の結果を予測できないことです。これはアプリケーションロジックであり、モデルレイヤーには実装しないでください。少なくとも、再利用可能なモデルレイヤーにつながる方法ではありません。


インターフェースの使用をお勧めします必要な機能のセットを説明します。 Foインスタンス、IDocumentインターフェース、IFancyインターフェース、INotSoFancyインターフェース。オブジェクトではなく機能について話しているため、これらのインターフェースはIDocumentまたは他の一般的なインターフェースから派生する場合としない場合があります。

このインターフェイスベースのソリューションを使用すると、クライアントコードは、指定されたドキュメントインスタンスが目的の機能をサポートしているかどうかを判断できます(ドキュメントインスタンスを目的のインターフェイスにキャストしようとすることにより)。クライアントコードは、それに応じて動作することができます。

0
oɔɯǝɹ

私はチャオの答えに完全に同意するので、繰り返しはしません。

ここでの本当の問題は、問題の領域について、もっと根本的な質問をする必要があることだと思います。すべてのコードは問題を解決するために書かれていることを忘れないでください。

そもそもなぜベースDocumentclassを書いているのですか?

Documentクラスから派生するのはなぜですか?

単なるインターフェースでは得られないことを導き出すことで何を得たいと思いますか?

この場合、2つの基本的なニーズがあるようです。ドキュメントタイプ間でコードを共有する必要があり、SectionAsからFancyDocumentを取得する必要があります。

この問題を解決するにはいくつかの方法があり、そのうち2つを選択しました。考慮していない代替アプローチは、継承の代わりに構成を使用することです。多くの場合、継承の問題は、それがジョブの間違ったツールであることの結果です。

代替オプションとして以下を検討してください。

sealed class Document 
{ 
    // some stuff 
}

class Fancy
{
    public Document Document { get; set; }
    public SectionA GetDocumentSectionA();
    // other fancy methods.
}

class NotFancy
{
    public Document Document { get; set; }
    // other non-fancy methods.
}

これで、ドキュメントタイプ間の境界がはるかに明確になったことがわかります。このアプローチのこの欠点は、両方のクラスを同じ厳密に型指定されたDocumentコンテナーに格納できないことです。それが重要な場合は、インターフェースの使用を検討するか、代わりに継承を使用できます。

0
Stephen