web-dev-qa-db-ja.com

C#の抽象クラスを定義として使用する

C++開発者として、私はC++ヘッダーファイルにかなり慣れています。コード内に強制的な "ドキュメント"を含めることは有益だと思います。そのため、通常はC#コードを読む必要があるときに悪い時間を過ごします。私が作業しているクラスのそのようなメンタルマップがありません。

ソフトウェアエンジニアとして、プログラムのフレームワークを設計しているとしましょう。 C++ヘッダーで行うのと同様に、すべてのクラスを実装されていない抽象クラスとして定義し、開発者に実装させるのは、あまりにもおかしいでしょうか。

誰かがこれがひどい解決策であるとわかる理由がいくつかあるのではないかと思いますが、理由はわかりません。このような解決策について、何を検討する必要がありますか?

21

これがC++で行われた理由は、コンパイラーの実装をより速くより簡単にすることと関係がありました。これはプログラミングを簡単にするための設計ではありませんでした。

ヘッダーファイルの目的は、コンパイラーが超高速の最初のパスを実行して、予想されるすべての関数名を把握し、それらにメモリの場所を割り当てることで、それらを定義するクラスがそれらを持っている場合でも、cpファイルで呼び出されたときに参照できるようにすることでした。まだ解析されていません。

現代の開発環境で古いハードウェア制限の結果を再現しようとすることはお勧めしません!

すべてのクラスにインターフェースまたは抽象クラスを定義すると、生産性が低下します。 その時に他に何ができましたか?また、他の開発者はこの規則に従いません。

実際、他の開発者があなたの抽象クラスを削除するかもしれません。これらの両方の条件を満たすコードでインターフェイスを見つけた場合は、削除しますとし、コードからリファクタリングします:1. Does not conform to the interface segregation principle2. Only has one class that inherits from it

もう1つは、Visual Studioに含まれているツールで、目的を自動的に達成するものです。

  1. Class View
  2. Object Browser
  3. Solution Explorerでは、三角形をクリックしてクラスを拡張し、その関数、パラメーター、戻り値の型を確認できます。
  4. ファイルタブの下に3つの小さなドロップダウンメニューがあり、最も右のメニューには現在のクラスのすべてのメンバーがリストされます。

C#でC++ヘッダーファイルを複製するために時間をかける前に、上記の1つを試してください。


さらに、これを行わない技術的な理由があります...最終的なバイナリが必要以上に大きくなります。マーク・ベニングフィールドによるこのコメントを繰り返します。

C++ヘッダーファイルの宣言は、生成されたバイナリの一部にはなりません。それらはコンパイラとリンカのためにあります。これらのC#抽象クラスは、生成されるコードの一部であり、何のメリットもありません。


また、Robert Harveyが技術的に言及したように、C#のヘッダーに最も近いものは、抽象クラスではなくインターフェイスになります。

44
TheCatWhisperer

まず、純粋に抽象クラスは、実際には多重継承を行うことができない単なるインターフェースであることを理解してください。

クラスを書き、インターフェースを抽出することは、頭の痛い活動です。そのため、リファクタリングを行います。それは残念です。この「すべてのクラスがインターフェースを取得する」パターンに従うと、混乱が生じるだけでなく、ポイントを完全に逃します。

インターフェースは、クラスが実行できることを単に正式に言い換えたものと考えるべきではありません。インターフェースは、それを必要とするクライアントコードを使用することによって課されたコントラクトと考える必要があります。

現在それを実装しているクラスが1つしかないインターフェースを作成するのにまったく問題はありません。実際に実装しているクラスがまったくない場合でも、実際には気にしません。私のコードを使用して何が必要かを考えているからです。インターフェイスは、使用するコードが要求するものを表現します。それがこれらの期待を満たす限り、後で来るものは何でもそれが好きなことをすることができます。

あるオブジェクトが別のオブジェクトを使用するたびにこれを行うわけではありません。境界を越えるときにこれを行います。 1つのオブジェクトが他のどのオブジェクトと通信しているのかを正確に知りたくない場合に行います。これが、ポリモーフィズムが機能する唯一の方法です。私のクライアントコードが話しているオブジェクトが変更される可能性が高いと私が思うときにそれを行います。私が使用しているものがStringクラスである場合、私は確かにこれをしません。 Stringクラスは素晴らしく、安定していて、私が変更するのを防ぐ必要はありません。

抽象化ではなく具体的​​な実装と直接やり取りする場合は、実装が変化しないと信頼できるほど十分に安定していると予測していることになります。

依存関係の逆転の原則 を和らげる方法があります。盲目的にこれをすべてに適用すべきではありません。抽象化を追加するとき、プロジェクトの存続期間中、クラスを実装するという選択が信頼できないと本当に言っていることになります。

これは、あなたが Open Closed Principle に従っていることを前提としています。この原則は、確立されたコードを直接変更することに関連するコストが大きい場合にのみ重要です。デカップリングオブジェクトがいかに重要であるかについて人々が意見を異にする主な理由の1つは、直接変更を行うときに全員が同じコストを経験するわけではないためです。コードベース全体を再テスト、再コンパイル、および再配布するのが簡単な場合、直接変更して変更の必要性を解決することで、この問題を非常に魅力的に単純化できます。

この質問に対する頭の痛い答えはありません。インターフェースまたは抽象クラスは、すべてのクラスに追加する必要があるものではなく、実装クラスの数を数え、それが不要であると判断することはできません。変化に対処することです。つまり、あなたは将来を見越しているのです。間違えても驚かないでください。隅に戻ることなく、できるだけシンプルにしてください。

したがって、コードを読みやすくするためだけに抽象化を記述しないでください。そのためのツールがあります。抽象化を使用して、分離が必要なものを分離します。

14
candied_orange

はい、それは恐ろしいでしょう(1)不要なコードを導入する(2)読者を混乱させるでしょう。

C#でプログラミングしたい場合は、C#を読むことに慣れる必要があります。他の人が書いたコードは、とにかくこのパターンには従いません。

5
JacquesB

ユニットテスト

インターフェイスや抽象クラスでコードを乱雑にする代わりに、ユニットテストを作成することを強くお勧めします(他の理由で保証されている場合を除く)。

適切に記述された単体テストは、クラスのインターフェイス(ヘッダーファイル、抽象クラス、インターフェイスなど)を記述するだけでなく、例として、必要な機能も記述します。

例:ヘッダーファイルmyclass.hを次のように記述した場合:

class MyClass
{
public:
  void foo();
};

代わりに、c#で次のようなテストを記述します。

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void MyClass_should_have_method_Foo()
    {
        //Arrange
        var myClass = new MyClass();
        //Act
        myClass.Foo();
        //Verify
        Assert.Inconclusive("TODO: Write a more detailed test");
    }
}

この非常に単純なテストは、ヘッダーファイルと同じ情報を伝えます。 (パラメーターなしの関数「Foo」を含む「MyClass」という名前のクラスが必要です)ヘッダーファイルはよりコンパクトですが、テストにははるかに多くの情報が含まれています。

注意:TDDのような方法論との衝突を他の開発者が激しく解決するために上級ソフトウェアエンジニアにテストを提供(失敗)させるようなプロセスですが、あなたの場合、それは大きな改善になるでしょう。

1
Guran