デザインパターンについて読むとき、このフレーズに出会います。
しかし、私はそれを理解していません、誰かが私にこれを説明できますか?
デザインパターンについて読むとき、このフレーズに出会います。
しかし、私はそれを理解していません、誰かが私にこれを説明できますか?
回答:
インターフェイスは単なるコントラクトまたはシグネチャであり、実装については何も知りません。
インターフェースを使用してコーディングすると、クライアントコードは常にファクトリによって提供されるインターフェースオブジェクトを保持します。ファクトリーによって返されるインスタンスはすべて、タイプが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
}
List
、タイプとしてaを使用する場合でも、を繰り返し呼び出すことにより、ランダムアクセスが高速であると想定できますget(i)
。
インターフェースは、オブジェクトとそのクライアント間のコントラクトと考えてください。つまり、インターフェイスは、オブジェクトが実行できること、およびそれらにアクセスするための署名を指定します。
実装は実際の動作です。たとえば、メソッドsort()があるとします。QuickSortまたはMergeSortを実装できます。インターフェイスが変更されない限り、クライアントコードでsortを呼び出すことは問題になりません。
Java APIや.NET Frameworkのようなライブラリーは、何百万ものプログラマーが提供されたオブジェクトを使用するため、インターフェースを多用します。これらのライブラリの作成者は、ライブラリを使用するすべてのプログラマーに影響を与えるため、これらのライブラリのクラスへのインターフェイスを変更しないように十分注意する必要があります。一方、実装は好きなだけ変更できます。
プログラマーとして実装に対してコーディングを行った場合、コードが変更されるとすぐにコードが機能しなくなります。したがって、このインターフェースの利点を次のように考えてください。
つまり、実装を直接使用するのではなく、抽象(抽象クラスまたはインターフェース)を使用するようにコードを作成する必要があります。
通常、実装はコンストラクターまたはメソッド呼び出しを通じてコードに注入されます。したがって、コードはインターフェイスまたは抽象クラスを認識しており、このコントラクトで定義されているすべてのものを呼び出すことができます。実際のオブジェクト(インターフェース/抽象クラスの実装)が使用されているので、呼び出しはオブジェクトで動作しています。
これは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();
}
基本クラスライブラリ(BCL)のもう1つの例は、ProviderBase
抽象クラスです。これは、いくつかのインフラストラクチャを提供します。重要なこととして、コード化すると、すべてのプロバイダー実装を互換的に使用できます。
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;
}
追加説明:あなたは複数の車を所有する車の所有者です。アウトソーシングしたいサービスを切り分けます。私たちの場合、すべての車のメンテナンス作業を外部委託したいと思います。
車のタイプとサービスプロバイダーの関連付けについて心配する必要はありません。メンテナンスのスケジュールを設定し、いつ呼び出すかを指定するだけです。適切なサービス会社が飛び込んでメンテナンス作業を行う必要があります。
代替アプローチ。
あなたは仕事を呼び出して、それを自分で行います。ここでは、適切なメンテナンス作業を行います。
2番目のアプローチの欠点は何ですか?あなたは、メンテナンスを行うための最良の方法を見つけることの専門家ではないかもしれません。あなたの仕事は、車を運転して楽しむことです。それを維持するためのビジネスではありません。
最初のアプローチの欠点は何ですか?会社探しなどの諸経費があります。レンタカー会社でないと努力に値しないかもしれません。
このステートメントはカップリングについてです。オブジェクト指向プログラミングを使用する1つの潜在的な理由は、再利用です。たとえば、アルゴリズムを2つの協調オブジェクトAとBに分割できます。これは、後で別のアルゴリズムを作成して、2つのオブジェクトのいずれかを再利用する場合に役立ちます。ただし、これらのオブジェクトが通信(メッセージの送信-メソッドの呼び出し)すると、相互間の依存関係が作成されます。しかし、一方を他方なしで使用したい場合は、Bを置き換える場合、他のオブジェクトCがオブジェクトAに対して何を行うかを指定する必要があります。これらの記述はインターフェースと呼ばれます。これにより、オブジェクトAは、インターフェイスに依存する別のオブジェクトと変更せずに通信できます。あなたが言及した声明は、アルゴリズム(またはより一般的にはプログラム)の一部を再利用することを計画している場合、インターフェースを作成し、それらに依存する必要があると述べています、
他の人が言ったように、それはあなたの呼び出しコードが抽象親についてのみ知っているべきであり、仕事をする実際の実装クラスではないということを意味します。
これを理解するのに役立つのは、常にインターフェイスにプログラムする必要がある理由です。理由はたくさんありますが、最も簡単に説明できるのは次の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