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

StackOverflow https://stackoverflow.com/questions/2697783

質問

デザインパターンについて読んでいると、このフレーズに遭遇します。

しかし、私にはそれが理解できません、誰かが私にこれを説明してもらえますか?

役に立ちましたか?

解決

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

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

これがあなたのための基本的な例です。

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

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

編集

上記の例を更新し、抽象的なスピーカーベースクラスを追加しました。このアップデートでは、すべてのスペイカーに「Sayhello」に機能を追加しました。すべてのスピーカーは「Hello World」を話します。これは、同様の機能を備えた一般的な機能です。クラス図を参照すると、スピーカーの抽象クラスがIspeakerインターフェイスを実装し、Speak()を抽象としてマークすることがわかります。これは、各スピーカーの実装がスピーカーごとに異なるため、Speakメソッドの実装を担当することを意味します。しかし、すべてのスピーカーは満場一致で「こんにちは」と言います。したがって、抽象スピーカークラスでは、「Hello World」と書かれたメソッドを定義し、各スピーカーの実装はSayhelloメソッドを導き出します。

スペインスピーカーがこんにちはと言うことができない場合を考えてみてください。その場合、スペイン語話者のためにSayhelloメソッドをオーバーライドし、適切な例外を提起することができます。

Ispeakerのインターフェイスに変更を加えていないことに注意してください。また、クライアントコードとスピーカーファクトリーも影響を受けずに依然としてありません。そして、これが私たちが達成したことです プログラミング間インターフェイス.

また、ベースの抽象クラススピーカーと各実装にいくつかのマイナーな変更を追加するだけで、この動作を達成することができ、元のプログラムを変更しません。これはあらゆるアプリケーションの望ましい機能であり、アプリケーションが簡単に保守可能になります。

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

他のヒント

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

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

Java APIや.NETフレームワークなどのライブラリは、数百万のプログラマーが提供されたオブジェクトを使用しているため、インターフェイスを豊富に使用しています。これらのライブラリの作成者は、ライブラリを使用してすべてのプログラマーに影響を与えるため、これらのライブラリのクラスにインターフェイスを変更しないことに非常に注意する必要があります。一方、彼らは好きなだけ実装を変更できます。

プログラマーとして、コードが変更されるとすぐに実装に反してコードする場合。このように、インターフェイスの利点を考えてみてください。

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

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

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

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

.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();
}

基本クラス ライブラリ (BCL) の別の例は次のとおりです。 ProviderBase 抽象クラス - これは何らかのインフラストラクチャを提供しますが、重要なことは、これに対してコードを作成した場合、すべてのプロバイダ実装を互換的に使用できることを意味します。

燃焼車時代に車のクラスを書く場合、このクラスの一部としてOilchange()を実装する可能性が非常に高くなります。しかし、電気自動車が導入されると、これらの車にはオイル変化が関係しておらず、実装がないため、問題が発生します。

問題の解決策は、CARクラスにPerformMantenance()インターフェイスを作成し、適切な実装内に詳細を非表示にすることです。各車の種類は、Performmentenance()のための独自の実装を提供します。車の所有者として、あなたが対処しなければならないのは履歴()であり、変更があるときに適応することを心配しないでください。

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

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

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

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

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

1)テスト。

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

workerclass-> dalclassただし、ミックスにインターフェイスを追加しましょう。

workerclass-> idal-> dalclass。

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

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

workerclass-> idal-> ifakedal。

2)再利用

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

workerclass-> idal-> dalclass

workerclass-> idal-> mongodbclass

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top