抽象クラスを既に持っている場合、インターフェイスを定義するのは理にかなっていますか?


12

いくつかのデフォルト/共有機能を持つクラスがあります。私abstract classはそれを使用します:

public interface ITypeNameMapper
{
    string Map(TypeDefinition typeDefinition);
}

public abstract class TypeNameMapper : ITypeNameMapper
{
    public virtual string Map(TypeDefinition typeDefinition)
    {
        if (typeDefinition is ClassDefinition classDefinition)
        {
            return Map(classDefinition);
        }
        ...

        throw new ArgumentOutOfRangeException(nameof(typeDefinition));
    }

    protected abstract string Map(ClassDefinition classDefinition);
}

ご覧のとおり、インターフェイスもありますITypeNameMapper。私がすでに抽象クラスを持っているTypeNameMapperか、それでabstract class十分な場合、このインターフェイスを定義するのは理にかなっていますか?

TypeDefinition この最小限の例でも抽象的です。


4
参考までに、OODでは、コードで型をチェックすることは、クラス階層が正しくないことを示しています。これは、チェックする型から派生クラスを作成するように指示しています。したがって、この場合、Map()メソッドを指定するインターフェイスITypeMapperが必要であり、TypeDefinitionクラスとClassDefinitionクラスの両方でそのインターフェイスを実装します。このように、ClassAssemblerはITypeMappersのリストを繰り返し処理し、それぞれに対してMap()を呼び出し、それぞれが独自の実装を持っているため、両方のタイプから目的の文字列を取得します。
ロドニーP.バルバティ

1
インターフェイスの代わりに基本クラスを使用することは、絶対に必要な場合を除いて、「ベースの書き込み」と呼ばれます。インターフェイスで実行できる場合は、インターフェイスで実行します。これにより、抽象クラスが便利なエキストラになります。artima.com/intv/dotnet.html 具体的には、リモート処理ではMarshalByRefObjectから派生する必要があるため、リモートにしたい場合は他から派生することはできません。
ベン

私は、状況に応じて切り替えることができたのと同じタイプのための多くのマッパーしたい場合RodneyP.Barbatiは動作しません@
コンラート

1
@Benが面白いベースを燃やしています。宝石をありがとう!
コンラッド

@Konradそれは間違っています-インターフェイスの別の実装を呼び出してインターフェイスを実装することを妨げるものはありません。したがって、各型には、その型の実装を介して公開するプラグ可能な実装があります。
ロドニーP.バルバティ

回答:


31

はい、C#ではインターフェイスを除き複数の継承を許可しないためです。

したがって、TypeNameMapperとSomethingelseMapperの両方であるクラスがある場合、次のことができます。

class MultiFunctionalClass : ITypeNameMapper, ISomethingelseMapper 
{
    private TypeNameMapper map1
    private SomethingelseMapper map2

    public string Map(TypeDefinition typeDefinition) { return map1.Map(typeDefintion);}

    public string Map(OtherDef otherDef) { return map2.Map(orderDef); }
}

4
@Liath:単一の責任は、実際には、なぜ継承が必要なのかに関する要因です。三つの可能な特性を考慮してくださいICanFlyICanRunICanSwim。特性(YYY、YYN、YNY、...、NNY、NNN)の組み合わせごとに1つずつ、8つのクリーチャークラスを作成する必要があります。SRPは、各動作(フライ、実行、水泳)を一度実装し、8つのクリーチャーに再利用可能に実装することを示しています。ここでは構成はさらに良くなりますが、構成を一瞬無視すると、SRPによる複数の個別の再利用可能な動作を継承/実装する必要性が既にわかるはずです。
フラット

4
@Konradそれは私が意味するものです。インターフェイスを作成し、それを必要としない場合、コストは3 LoCです。インターフェイスを作成せずに必要な場合は、アプリケーション全体のリファクタリングがコストになる可能性があります。
ユアン

3
(1)インターフェースの実装は継承ですか?(2)質問と回答の両方に資格が必要です。"場合によります。" (3)多重継承には警告ステッカーが必要です。(4)Kのひどい形式的なinterface不正行為を示すことができます。ベースにあるべき冗長な実装-独立して進化しています!、テンプレートメソッドの不足、壊れたカプセル化、存在しない抽象化のためにこのジャンクを駆動する条件付きロジックの変更。私のお気に入り:それぞれが独自の1メソッドを実装する1メソッドクラスinterface。...などなど
レーダーボブ

3
コードには目に見えない摩擦係数があります。余計なインターフェイスを読むことを余儀なくされたとき、あなたはそれを感じる。次に、インターフェイスの実装が抽象クラスに移動するときに、スペースシャトルが大気中にバタバタするように、それ自体が加熱されるのを示します。それはトワイライトゾーンであり、これがシャトルチャレンジャーです。私の運命を受け入れて、私は大胆不敵で戸惑う不思議に荒れ狂うプラズマを見ます。容赦ない摩擦が完璧な外観を引き裂き、砕けたハルクを雷鳴で生産に置きます。
レーダーボブ

4
@radarbob Poetic。^ _ ^同意します。インターフェイスのコストは低いものの、存在しないわけではありません。それらを書くことはそれほど多くはありませんが、デバッグの状況では実際の動作に到達するのにさらに1〜2ステップあります。図書館では、通常、それだけの価値があります。しかし、アプリケーションでは、リファクタリングは時期尚早な一般化よりも優れています。
Errorsatz

1

インターフェイスと抽象クラスの目的は次のとおりです。

  • インターフェイスはAPIを定義し、実装ではなくクライアントに属します。
  • クラスが実装を共有する場合、抽象クラスの恩恵を受けることができます。

この例でinterface ITypeNameMapperは、クライアントのニーズを定義し、abstract class TypeNameMapper値を追加していません。


2
抽象クラスもクライアントに属します。それがどういう意味かわかりません。publicクライアントに属するすべてのもの。TypeNameMapper実装者がいくつかの一般的なロジックを記述する必要がないため、多くの価値を追加しています。
コンラッド

2
インターフェイスがとして提示されるかどうかinterfaceは、C#が完全な多重継承を禁止するという点でのみ関連します。
デュプリケータ

0

インターフェイスの概念全体は、共有APIを持つクラスのファミリをサポートするために作成されました。

これは、インターフェースの使用は、その仕様の実装が複数あることを意味することを明示的に言っています。

DIフレームワークは、実装が1つしかない場合でも、多くの場合インターフェイスが必要なため、ここで水を汚しました-私にとって、これはほとんどの場合、新しい呼び出しのより複雑で遅い手段であるために不合理なオーバーヘッドですが、それが何であるかです。

答えは質問自体にあります。抽象クラスがある場合、共通のAPIを使用して複数の派生クラスを作成する準備をしています。したがって、インターフェースの使用が明確に示されています。


2
「抽象クラスがある場合、...インターフェイスの使用が明確に示されています。」-私の結論は逆です:抽象クラスがある場合は、それをインターフェイスであるかのように使用します(DIが抽象クラスを使用しない場合は、別の実装を検討してください)。実際に機能しない場合にのみ、インターフェースを作成します。後でいつでも作成できます。
maaartinus

1
同上、@ maaartinus。そして、「...それがインターフェースであるかのように」でさえありません。クラスのパブリックメンバがあるインターフェイス。また、「後でいつでも実行できます」も同じです。これは、設計の基礎101の核心になります
。–レーダーボブ

@maaartinus:最初からインターフェースを作成するが、あちこちで抽象クラス型の参照を使用する場合、インターフェースの存在は何の助けにもなりません。ただし、クライアントコードが可能な限り抽象クラス型ではなくインターフェイス型を使用する場合、内部で抽象クラスとは非常に異なる実装を作成する必要が生じた場合に、事態が大幅に促進されます。その時点でクライアントコードを変更してインターフェイスを使用することは、最初からクライアントコードを設計するよりもはるかに苦痛です。
-supercat

1
@supercatあなたがライブラリを書いていると仮定すると同意できます。アプリケーションの場合、すべての出現を一度に置き換える問題は見られません。私のEclipseはそれを行うことができます(JavaではなくC#)が、できなかった場合、それは同じようにfind ... -exec perl -pi -e ...、おそらく些細なコンパイルエラーを修正します。私のポイントは:(現在)役に立たないものを持たないことの利点は、可能な将来の節約よりもはるかに大きいことです。
maaartinus

interfaceコードとしてマークアップした場合(interfaceクラス/関数/セットなどの抽象的なインターフェースではなくC#-s について話している場合)、最初の文は正しいでしょう。継承。" interfaceMIがあれば、s は必要ありません。
デュプリケータ

0

質問に明示的に答えたい場合、著者は、「抽象クラスTypeNameMapperを既に持っている場合、または抽象クラスで十分な場合、このインターフェイスを定義しても意味がありますか?」と言います。

はいインターフェイスがない場合の抽象基本クラス。

構築しようとしているAPIについて十分に考えていないことは明らかです。最初にAPIを作成し、必要に応じて部分的な実装を提供します。抽象基本クラスがコードの型チェックを行うという事実は、それが正しい抽象化ではないことを伝えるのに十分です。

OODのほとんどの場合と同様に、グルーブに入ってオブジェクトモデルを適切に処理すると、コードから次に何が来るかが通知されます。インターフェースから始めて、必要なクラスにそのインターフェースを実装します。同様のコードを書いていることに気付いたら、それを抽象基本クラスに抽出します-それが重要なインターフェースであり、抽象基本クラスは単なるヘルパーであり、複数ある場合があります。


-1

これは、アプリケーションの残りの部分を知らずに答えることは非常に困難です。

ある種のDIモデルを使用しており、コードを可能な限り拡張可能に設計していると仮定すると(そうすればTypeNameMapper簡単に新しいものを追加できます)、私はイエスと言います。

の理由:

  • 基本クラスが実装するため、効果的に無料で入手できinterfaceます。どの子実装でも心配する必要はありません
  • ほとんどのIoCフレームワークとモッキングライブラリはを期待していinterfaceます。それらの多くは抽象クラスで動作しますが、それは常に与えられているわけではなく、可能な限りライブラリの他の99%のユーザーと同じ道をたどることを強く信じています。

以下に対する理由:

  • 厳密に言えば(あなたが指摘したように)あなたは本当にする必要はありません
  • class/ interfaceが変更された場合、少しオーバーヘッドが追加されます

すべてのことを考慮して、私はあなたがinterface。その理由はごくわずかですが、モックやIoCでアブストラクトを使用することは可能ですが、多くの場合、インターフェイスを使用する方がはるかに簡単です。しかし、最終的には、同僚のコードが反対になっても、私は同僚のコードを批判しません。

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