web-dev-qa-db-ja.com

「実装ではなくインターフェースへのプログラム」とはどういう意味ですか?

デザインパターンについて読むとき、このフレーズにつまずきます。

しかし、私はそれを理解していません、誰かが私のためにこれを説明できますか?

115

インターフェイスは単なる契約または署名であり、実装については何も知りません。

インターフェースに対するコーディングとは、クライアントコードが常にファクトリーによって提供されるインターフェースオブジェクトを保持することです。ファクトリーによって返されるインスタンスは、任意のファクトリー候補クラスが実装しなければならないタイプのインターフェースになります。このように、クライアントプログラムは実装を心配せず、インターフェイスシグネチャによってすべての操作を実行できるかどうかが決まります。これは、実行時にプログラムの動作を変更するために使用できます。また、メンテナンスの観点からはるかに優れたプログラムを作成するのに役立ちます。

基本的な例を次に示します。

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

alt text

これは単なる基本的な例であり、原理の実際の説明はこの答えの範囲を超えています。

編集

上記の例を更新し、抽象的なSpeaker基本クラスを追加しました。この更新では、すべてのSpakerに「SayHello」の機能を追加しました。すべてのスピーカーは「Hello World」を話します。したがって、これは同様の機能を持つ一般的な機能です。クラス図を参照すると、Speaker抽象クラスがISpeakerインターフェイスを実装し、Speak()を抽象としてマークしていることがわかります。つまり、Speakメソッドは、Speakerごとに異なるため、Speakメソッドの実装を担当します。しかし、全員が満場一致で「こんにちは」と言います。したがって、抽象Speakerクラスでは、「Hello World」というメソッドを定義し、各Speaker実装はSayHelloメソッドを派生させます。

SpanishSpeakerがHelloを発声できない場合を考えてみましょう。その場合、SpanishSpeakerのSayHelloメソッドをオーバーライドして、適切な例外を発生させることができます。

インターフェイスISpeakerに変更を加えていないことに注意してください。クライアントコードとSpeakerFactoryも変更されていないままです。そして、これがProgramming-to-Interfaceによって達成されることです。

また、基本動作の抽象クラスSpeakerと各実装に若干の修正を追加するだけでこの動作を実現でき、元のプログラムは変更されません。これはあらゆるアプリケーションの望ましい機能であり、アプリケーションを簡単に保守可能にします。

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}

alt text

131

インターフェースは、オブジェクトとそのクライアント間の契約と考えてください。これは、オブジェクトが実行できること、およびそれらのことにアクセスするための署名を指定するインターフェースです。

実装は実際の動作です。たとえば、メソッドsort()があるとします。 QuickSortまたはMergeSortを実装できます。インターフェイスが変更されない限り、sortを呼び出すクライアントコードには関係ありません。

Java APIや.NET Frameworkは、提供されたオブジェクトを何百万人ものプログラマが使用するため、インターフェイスを頻繁に使用します。これらのライブラリの作成者は、インターフェイスを変更しないように注意する必要がありますライブラリを使用するすべてのプログラマに影響するため、これらのライブラリのクラスに変更します。

プログラマーとして実装に対してコーディングを行うと、コードが変更されるとすぐにコードが機能しなくなります。このようにインターフェースの利点を考えてください:

  1. オブジェクトを使いやすくするために、知る必要のないものを隠します。
  2. オブジェクトがどのように振る舞うかのコントラクトを提供するので、それに依存することができます
26

つまり、実装ではなく抽象化(抽象クラ​​スまたはインターフェイス)を直接使用するようにコードを作成する必要があります。

通常、実装はコンストラクターまたはメソッド呼び出しを通じてコードに注入されます。したがって、コードはインターフェイスまたは抽象クラスを認識し、このコントラクトで定義されているものを呼び出すことができます。実際のオブジェクト(インターフェイス/抽象クラスの実装)が使用されるため、呼び出しはオブジェクトに対して実行されます。

これは Liskov Substitution Principle (LSP)、 SOLID 原則のL。

.NETの例は、IListまたはListの代わりにDictionaryを使用してコーディングすることです。したがって、コード内でIListを交換可能に実装するクラスを使用できます。 :

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

Base Class Library(BCL)の別の例は ProviderBase 抽象クラスです-これは何らかのインフラストラクチャを提供し、重要なこととして、それに対してコーディングする場合、すべてのプロバイダー実装を交換可能に使用できることを意味します。

15
Oded

Combustion-Car時代に車のクラスを作成する場合、このクラスの一部としてoilChange()を実装する可能性が高くなります。しかし、電気自動車が導入されると、これらの車にオイル交換がなく、実装も行われないため、問題が発生します。

この問題の解決策は、CarクラスにperformMaintenance()インターフェースを用意し、適切な実装内に詳細を隠すことです。各Carタイプは、performMaintenance()の独自の実装を提供します。車のオーナーとして対処する必要があるのは、performMaintenance()だけであり、変更があった場合に適応することを心配しません。

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}

追加説明:あなたは複数の車を所有している車の所有者です。アウトソーシングするサービスを作成します。私たちのケースでは、すべての車のメンテナンス作業を外部委託したいと考えています。

  1. すべての自動車とサービスプロバイダーに有効な契約(インターフェース)を特定します。
  2. サービスプロバイダーは、サービスを提供するメカニズムを提供します。
  3. 車種とサービスプロバイダーの関連付けについて心配する必要はありません。メンテナンスをスケジュールし、呼び出すタイミングを指定するだけです。適切なサービス会社が介入して保守作業を実行する必要があります。

    代替アプローチ。

  4. すべての車に適した作業(新しいインターフェイスのインターフェイスである可能性があります)を特定します。
  5. Yoサービスを提供するメカニズムを使用します。基本的には、実装を提供します。
  6. 作業を呼び出して、自分で実行します。ここでは、適切なメンテナンス作業を行います。

    2番目のアプローチの欠点は何ですか?あなたは、メンテナンスを行うための最良の方法を見つけることの専門家ではないかもしれません。あなたの仕事は車を運転して楽しむことです。それを維持するビジネスではないこと。

    最初のアプローチのマイナス面は何ですか?会社を見つけるなどのオーバーヘッドがあります。あなたがレンタカー会社でない限り、努力する価値はないかもしれません。

5
Raghav Navada

このステートメントは、カップリングに関するものです。オブジェクト指向プログラミングを使用する理由の1つは、再利用です。したがって、たとえば、2つの共同オブジェクトAとBにアルゴリズムを分割できます。これは、2つのオブジェクトのいずれかを再利用する別のアルゴリズムを後で作成するのに役立つ場合があります。ただし、これらのオブジェクトが通信する(メッセージを送信する-メソッドを呼び出す)場合、相互に依存関係が作成されます。ただし、一方を他方なしで使用する場合は、Bを置き換える場合、オブジェクトAに対して他のオブジェクトCが何を実行するかを指定する必要があります。これらの説明はインターフェイスと呼ばれます。これにより、オブジェクトAは、変更することなく、インターフェイスに依存する別のオブジェクトと通信できます。あなたが言及した声明は、アルゴリズムの一部(またはより一般的にはプログラム)を再利用する予定がある場合、インターフェイスを作成して依存する必要があるため、他のオブジェクトを変更せずにいつでも具体的な実装を変更することができる宣言されたインターフェース。

4

他の人が言ったように、それはあなたの呼び出しコードは抽象的な親についてのみ知っていて、作業を行う実際の実装クラスではないことを意味します。

これを理解するのに役立つのは、常にインターフェイスにプログラムする必要がある理由です。多くの理由がありますが、説明が最も簡単なのは次の2つです。

1)テスト。

1つのクラスにデータベースコード全体があるとします。私のプログラムが具象クラスについて知っている場合、そのクラスに対して実際に実行することによってのみコードをテストできます。私は->を「話し合い」を意味するために使用しています。

WorkerClass-> DALClassただし、インターフェイスを追加してみましょう。

WorkerClass-> IDAL-> DALClass。

したがって、DALClassはIDALインターフェイスを実装し、ワーカークラスのみがこれを介して呼び出します。

コードのテストを作成する場合、データベースのように機能する単純なクラスを作成できます。

WorkerClass-> IDAL-> IFakeDAL。

2)再利用

上記の例に従って、SQL Server(具体的なDALClassが使用する)からMonogoDBに移行したいとします。これには大きな作業が必要になりますが、インターフェイスにプログラムした場合はそうではありません。その場合、新しいDBクラスを作成し、変更します(ファクトリー経由)

WorkerClass-> IDAL-> DALClass

WorkerClass-> IDAL-> MongoDBClass

2
Mathieson

インターフェイスは機能を記述します。命令型コードを書くときは、特定の型やクラスではなく、使用している機能について話します。

1
rektide