デザインパターンについて読んでいると、このフレーズに出くわします。
でもよく分からないので誰か説明してくれませんか?
デザインパターンについて読んでいると、このフレーズに出くわします。
でもよく分からないので誰か説明してくれませんか?
インターフェイスは単なる契約または署名であり、実装については何も知りません。
インターフェイス手段に対するコーディングでは、クライアント コードは、ファクトリによって提供されるインターフェイス オブジェクトを常に保持します。ファクトリによって返されるインスタンスは、ファクトリ候補クラスが実装している必要がある 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
インターフェイスを実装し、 を抽象としてマークしていることがわかります。これは、各 Speaker 実装がから まで変化するため、メソッドのSpeak()
実装を担当することを意味します。しかし、スピーカー全員が満場一致で「こんにちは」と言います。したがって、抽象 Speaker クラスでは、「Hello World」と言うメソッドを定義し、各実装がメソッドを派生させます。Speak()
Speaker
Speaker
Speaker
SayHello()
SpanishSpeaker
こんにちはと言うことができない場合を考えてみてください。その場合SayHello()
、スペイン語話者のメソッドをオーバーライドして、適切な例外を発生させることができます。
インターフェイス ISpeaker には変更を加えていません。また、クライアント コードと SpeakerFactory も影響を受けず、変更されません。そして、これがProgramming-to-Interfaceによって達成されるものです。
そして、基本抽象クラス Speaker を追加し、Each 実装にいくつかのマイナーな変更を加えて、元のプログラムを変更せずに、この動作を実現できます。これはどのアプリケーションにも望まれる機能であり、アプリケーションの保守を容易にします。
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
}
インターフェイスは、オブジェクトとそのクライアントの間の契約と考えてください。つまり、オブジェクトが実行できることと、それらにアクセスするための署名を指定するインターフェイスです。
実装は実際の動作です。たとえば、メソッド sort() があるとします。QuickSort または MergeSort を実装できます。インターフェイスが変更されない限り、それは sort を呼び出すクライアント コードには関係ありません。
Java API や .NET Framework などのライブラリは、提供されたオブジェクトを何百万人ものプログラマが使用するため、インターフェイスを多用します。これらのライブラリの作成者は、ライブラリを使用するすべてのプログラマに影響を与えるため、これらのライブラリ内のクラスへのインターフェイスを変更しないように細心の注意を払う必要があります。一方、実装は好きなだけ変更できます。
プログラマーとして、実装に対してコーディングすると、コードが変更されるとすぐに機能しなくなります。したがって、インターフェースの利点を次のように考えてみてください。
これは、実装を直接行うのではなく、抽象化 (抽象クラスまたはインターフェース) を使用するようにコードを作成する必要があることを意味します。
通常、実装はコンストラクターまたはメソッド呼び出しを介してコードに挿入されます。したがって、コードはインターフェイスまたは抽象クラスを認識しており、このコントラクトで定義されているものをすべて呼び出すことができます。実際のオブジェクト (インターフェース/抽象クラスの実装) が使用されるため、呼び出しはオブジェクトに対して実行されます。
これは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();
}
Base Class Library (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 を置き換えた場合にオブジェクト A に対して他のオブジェクト C が何をするべきかを指定する必要があります。これらの記述はインターフェースと呼ばれます。これにより、オブジェクト A はインターフェイスに依存する別のオブジェクトと変更することなく通信できます。あなたが言及した声明は、アルゴリズム(またはより一般的にはプログラム)の一部を再利用する予定がある場合は、インターフェイスを作成してそれらに依存する必要があることを示しています。
インターフェイスは機能を記述します。命令型コードを記述するときは、特定の型やクラスではなく、使用している機能について話してください。