10

SOLID の原則と依存関係の反転に関する記事をいくつか読んでいます。私の観点からは、インターフェイスを使用してクラスと対話する必要があります。私のクラスは、インターフェイスを使用してチャットしています。

最初の質問:

私は抽象クラスを使用していますが、コードの 2 番目の部分ではインターフェイスを使用しています。

使用法1


namespace DependencyInjection
{

    public interface IMessage
    {

    }
    public abstract class Message
    {
        public abstract void Get();
        public abstract void Send();
    }

    public class Sms : Message, IMessage
    {
        public override void Get()
        {
            Console.WriteLine("Message Get!");
        }
        public override void Send()
        {
            Console.WriteLine("Message Send!");
        }
    }

    public class MessageManager
    {
        private IMessage _message;

        public Sms Sms
        {
            get { return _message as Sms; }
            set { _message = value; }
        }

        public MessageManager(IMessage message)
        {
            _message = message;
        }

    }
}

使用法:



    class Program
    {
        static void Main(string[] args)
        {
            MessageManager msg = new MessageManager(new Sms());
            msg.Sms.Get();
            msg.Sms.Send();
            Console.Read();
        }
    }

使い方2


namespace DependencyInjection
{

    public interface IMessage
    {
        public  void Get();
        public  void Send();
    }


    public class Sms :  IMessage
    {
        public  void IMessage.Get()
        {
            Console.WriteLine("Message Get!");
        }
        public  void IMessage.Send()
        {
            Console.WriteLine("Message Send!");
        }
    }

    public class MessageManager
    {
        private IMessage _message;

        public Sms Sms
        {
            get { return _message as Sms; }
            set { _message = value; }
        }

        public MessageManager(IMessage message)
        {
            _message = message;
        }

    }
}

使用法 1 と使用法 2 の違いは何ですか? 使用法 1 または使用法 2 を選択するのはいつですか?

4

6 に答える 6

26

重複したコードと戦うためにここにクラスを抽象化します。インターフェイス - コントラクト (API) を定義します。

インターフェースに依存 - 依存関係のコントラクト (API) を記述するだけで、簡単にモックできます。したがって、インターフェースから始めます。

public interface IMessage
{
    void Get(); // modifiers like public are not allowed here
    void Send();
}

そして、ここに依存クラスがあります。これは、抽象化 (つまり、インターフェイス) のみに依存する必要があります。

public class MessageManager
{
    private IMessage _message;

    // depend only on abstraction 
    // no references to interface implementations should be here
    public IMessage Message
    {
        get { return _message; }
        set { _message = value; }
    }

    public MessageManager(IMessage message)
    {
        _message = message;
    }
}

次に、インターフェイスを実装するクラスを作成します。

public class Sms : IMessage
{
    // do not use explicit implementation 
    // unless you need to have methods with same signature
    // or you want to hide interface implementation
    public void Get()
    {
        Console.WriteLine("Message Get!");
    }

    public void Send()
    {
        Console.WriteLine("Message Send!");
    }
}

MessageManagerこれで、依存関係が逆転し、Smsのみに依存しIMessageます。IMessage任意の実装を注入できますMessageManager(MessageManager は OCP に適合するようになりました - 拡張用に開いていますが、変更用には閉じています)。

複数の実装者でコードが重複している場合にのみ、基本抽象メッセージ クラスを作成します。IMessage抽象クラス (重複したコードを移動する場所) を作成する場合、コントラクトは同じままであるため、インターフェイスを変更しないでください。IMessage元のインターフェイスから基本クラスを継承するだけです。

于 2012-11-18T18:30:33.153 に答える
2

インターフェイスの元の定義は、すべての純粋仮想メソッド(つまり、抽象メソッド)を持つ抽象クラスです。これは、C++でインターフェイスを記述する方法です。デフォルトの定義で仮想関数を作成していない場合は、抽象クラスはまったく必要ありません。Messageクラスの子に継承させたい(そしてオーバーライドを許可したい)デフォルトの機能がある場合は、抽象クラスを使用します。抽象クラスは、保護されたメソッドやプロパティおよびフィールドを定義することもできます。これらは、抽象クラスから継承するクラスでは使用できますが、抽象クラスを使用するクラスでは使用できません。インターフェイスのすべてのメソッドはパブリックになります。インターフェースをレイアウトした場合は問題ありません。

于 2012-11-18T18:01:09.607 に答える
1

インターフェイスよりも抽象クラスを選択する理由は、再利用可能なコードに関係しています。依存性注入を使用しているという事実は、この答えを変えません。

すべてのサブクラスに共通の機能がありますか? その場合、基本クラスで定義できます。

テンプレート メソッド デザイン パターンを使用する必要がありますか? その場合、アルゴリズムは基本クラスに存在します。

いつものように、答えは「場合による」です。インターフェイスは契約を指定するだけで実装がないため、これらのアプローチをインターフェイスで行うことはできません。コントラクトだけで十分な場合は、インターフェースを使用してください。サブクラス間でコードを共有する必要がある場合は、抽象クラスを使用してください。

于 2012-11-18T17:29:53.503 に答える
1

私が見たものに基づいて、実際にはインターフェイスをまったく使用していません。確かにメソッドを実装しますが、消費するクラスは実装を実際に認識したり、気にしたりする必要はありません。したがって、Sms への参照やキャストは実際には表示されません。Unity、Ninject、structuremap などの IoC フレームワークを使用する必要があります。IMessage を返すパブリック プロパティが実際に必要な場合は、Sms ではなく IMessage を返す必要があります。それを行う必要があるかどうかは別の会話です。

つまり、最初の Usage には IMessage に何も含まれていないため、価値がありません。また、インターフェースの複数の実装間で共通の機能を処理するために、抽象/基本クラスをよく使用します。あなたのシナリオでは、抽象クラスは必要ありません。コードなしで抽象メソッドを作成するのは、抽象クラスが実際にそのメソッドを何らかの能力で起動するが、派生クラスが機能を実装することを期待する場合だけです。

とにかく、あなたの質問に答えるには、使用法 #2 が正しい解決策に近いようですが、Sms への参照を削除して、IoC コンテナーにそれを処理させてください。

于 2012-11-18T17:24:08.773 に答える
1

インターフェイスには実装コードは含まれず、実装者が特定のクラスからその実装を派生させることはありません。基本クラス (抽象かどうかに関係なく) には、既にいくつかのロジックが含まれている場合があります。これは利点であると同時に、望ましくない制約でもあります。

もちろん、両方のアプローチを組み合わせることができます。インターフェイスに対して定義してプログラミングすると同時に、このインターフェイスを実装する基本クラス (または複数の基本クラス) を提供し、実装者のタスクを簡素化するいくつかの基本的なロジックを証明します。インターフェイスを実装する人は、簡単な方法で基本クラスを拡張するか、まったく新しいものを作成してインターフェイスを直接実装するかを決定できます。

.NET Framework クラス ライブラリは、独自の特殊なコレクションを作成するためのベースとして使用できる、Collection<T>またはKeyedCollection<TKey,TItem>その両方を実装するコレクションの基本クラスを提供します。IList<T>もちろん、ゼロから始めてIList<T>直接実装することもできます。

于 2012-11-18T17:27:27.827 に答える
1

それらを聞くいくつかの理由を除いて、提供された他の回答に追加することはあまりありません。

抽象メソッドのみを持つ抽象クラスは、本質的にインターフェースです。違いは、何らかの形の仮想実装を持つことができるということであり、そこに落とし穴があります。基本クラスを使用して依存関係を隠すことで重複を削減しようとすると、非常に簡単になります。

実行間で保持する必要がある一連のオブジェクトがあるとします。保存機能を基本クラスに追加して、他の人が保存の仕組みについて心配する必要がないようにしたいと思います。問題は、それを完全に隠すと、実装をテストしなければならないテストの悪夢を作成するか、多くの機能が統合テストのみに追いやられることです。保存機能に戦略を使用すると、問題が完全に解決され、2 つを簡単に組み合わせることができます。

問題は、単に悪いというよりも誘惑です。継承はDI を止めるものではありませんが、DI を促進するものでもありません。SOLID と DI を使用しようとしている場合は、継承を避けた方がよいかもしれません。

于 2013-10-09T19:45:58.023 に答える