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


回答:


148

インターフェイスは単なるコントラクトまたはシグネチャであり、実装については何も知りません。

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

ここにあなたのための基本的な例があります。

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
}

代替テキスト

これは基本的な例にすぎず、原則の実際の説明はこの回答の範囲を超えています。

編集

上記の例を更新し、抽象Speaker基本クラスを追加しました。今回のアップデートでは、すべてのスピーカーに「SayHello」の機能を追加しました。すべてのスピーカーが「Hello World」を話します。これは、同様の機能を持つ共通の機能です。クラス図を参照してください、あなたがいることを見つけるSpeaker抽象クラスが実装するISpeakerインタフェースやマークSpeak()、各スピーカーの実装が実装するための責任があることを抽象どの手段として、Speak()それから変化するための方法をSpeakerしますSpeaker。しかし、すべてのスピーカーは満場一致で「こんにちは」と言います。したがって、抽象Speakerクラスでは、「Hello World」と言うメソッドを定義し、Speaker実装ごとにSayHello()メソッドを派生させます。

SpanishSpeakerこんにちはと言うことができない場合を考えてください。その場合、SayHello()スペイン語スピーカーのメソッドをオーバーライドして適切な例外を発生させることができます。

Interface ISpeakerには変更を加えていません。また、クライアントコードとSpeakerFactoryも影響を受けません。そして、これが私たちがプログラミングからインターフェースへと達成することです。

また、各実装に基本抽象クラス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
}

代替テキスト


19
インターフェイスへのプログラミングは、参照変数のタイプだけに関するものではありません。また、実装について暗黙の前提を使用しないことも意味します。たとえばList、タイプとしてaを使用する場合でも、を繰り返し呼び出すことにより、ランダムアクセスが高速であると想定できますget(i)
ヨアヒムザウアー

16
ファクトリはインターフェイスへのプログラミングに直交していますが、この説明では、まるでそれらがその一部であるかのように思われると思います。
T。

@トゥーン:あなたに同意します。プログラミングからインターフェースへの非常に基本的でシンプルな例を提供したいと思いました。少数の鳥や動物のクラスにIFlyableインターフェイスを実装することで質問者を混乱させたくありませんでした。
これ。__curious_geek

@この。代わりに抽象クラスまたはファサードパターンを使用した場合でも、「インターフェイスへのプログラム」と呼ばれますか?または、明示的にインターフェイスを使用してクラスに実装する必要がありますか?
never_had_a_name 2010

1
画像の作成に使用したUMLツールは何ですか?
Adam Arold 2013年

29

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

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

Java APIや.NET Frameworkのようなライブラリーは、何百万ものプログラマーが提供されたオブジェクトを使用するため、インターフェースを多用します。これらのライブラリの作成者は、ライブラリを使用するすべてのプログラマーに影響を与えるため、これらのライブラリのクラスへのインターフェイスを変更しないように十分注意する必要があります。一方、実装は好きなだけ変更できます。

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

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

これは、オブジェクトを実行するために契約していることに注意する必要があることを意味します。この例では、必ずしも安定したソートではなく、ソートのみを契約していると仮定します。
ペンガート2010

したがって、ライブラリのドキュメントで実装について言及されていないのと同様に、これらは含まれているクラスインターフェイスの説明にすぎません。
ジョーIddon 2018年

17

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

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

これはLiskov Substitution Principle(LSP)のサブセットであり、SOLID原則のLです。

.NETの例はIListListまたはの代わりにコーディングするDictionaryことです。そのためIList、コード内で互換的に実装される任意のクラスを使用できます。

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

基本クラスライブラリ(BCL)のもう1つの例は、ProviderBase抽象クラスです。これは、いくつかのインフラストラクチャを提供します。重要なこととして、コード化すると、すべてのプロバイダー実装を互換的に使用できます。


しかし、クライアントはどのようにしてインターフェースと対話し、その空のメソッドを使用できますか?
never_had_a_name 2010

1
クライアントはインターフェースと対話しませんが、インターフェースを通じて:)オブジェクトはメソッド(メッセージ)を通じて他のオブジェクトと対話し、インターフェースは一種の言語です-特定のオブジェクト(人)が英語(IList)を実装する(話す)ことを知っている場合)、そのオブジェクト(彼もイタリア人であること)について詳しく知る必要がなくても使用できます。そのコンテキストでは必要ないためです(助けを求めたい場合は、彼がイタリア語も話すことを知る必要はありません)。英語を理解している場合)。
GabrielŠčerbák2010

ところで。IMHO Liskov置換の原則は、継承のセマンティックスに関するものであり、インターフェイスとは何の関係もありません。これは、継承のない言語でも見られます(Googleから移動)。
GabrielŠčerbák2010

5

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

問題の解決策は、CarクラスにperformMaintenance()インターフェースを用意し、適切な実装内に詳細を隠すことです。Carタイプごとに、performMaintenance()の独自の実装が提供されます。Carの所有者として対処する必要があるのは、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. サービスを提供するためのメカニズム出てきます。基本的に、実装を提供します。
  6. あなたは仕事を呼び出して、それを自分で行います。ここでは、適切なメンテナンス作業を行います。

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

    最初のアプローチの欠点は何ですか?会社探しなどの諸経費があります。レンタカー会社でないと努力に値しないかもしれません。


4

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


2

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

これを理解するのに役立つのは、常にインターフェイスにプログラムする必要がある理由です。理由はたくさんありますが、最も簡単に説明できるのは次の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


1

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

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