インターフェースの理由は本当に私を免れます。私が理解していることから、それはC#には存在しない(またはそう言われた)存在しないマルチ継承のための回避策のようなものです。
私が見るのは、あなたがいくつかのメンバーと関数を事前に定義し、それらをクラスで再定義する必要があるということだけです。したがって、インターフェースを冗長化します。それはただの構文のように感じます...まあ、私にとってはジャンクです(不快な意味はありません。不要なもののようにジャンクしてください)。
スタックオーバーフローの異なるC#インターフェイススレッドから取得した以下の例では、インターフェイスの代わりにPizzaという基本クラスを作成します。
簡単な例(異なるスタックオーバーフローの寄与から取得)
public interface IPizza
{
public void Order();
}
public class PepperoniPizza : IPizza
{
public void Order()
{
//Order Pepperoni pizza
}
}
public class HawaiiPizza : IPizza
{
public void Order()
{
//Order HawaiiPizza
}
}
ポイントは、インターフェイスが契約を表すことです。実装クラスが持つ必要のあるパブリックメソッドのセット。技術的には、インターフェイスは構文、つまり、存在するメソッド、取得する引数、および返すもののみを管理します。通常は、セマンティクスもカプセル化しますが、それはドキュメントによってのみです。
その後、インターフェイスのさまざまな実装を使用して、それらを自由に交換できます。この例では、すべてのピザインスタンスはIPizza
であるため、不明なピザタイプのインスタンスを処理する場所であればどこでもIPizza
を使用できます。タイプがIPizza
から継承するインスタンスは、Order()
メソッドがあるため、順序付け可能であることが保証されています。
Pythonは静的に型付けされていないため、型は実行時に保持および検索されます。そのため、任意のオブジェクトでOrder()
メソッドを呼び出すことができます。オブジェクトがそのようなメソッドを持ち、おそらく肩をすくめて、ない場合は"Meh。"と言う限り、ランタイムは幸せです。 C#ではそうではありません。コンパイラーは正しい呼び出しを行う責任があり、ランダムなobject
がある場合、コンパイラーは実行中のインスタンスにそのメソッドがあるかどうかをまだ知りません。コンパイラの観点からは、検証できないため無効です。 (リフレクションまたはdynamic
キーワードを使用してこのようなことを行うことができますが、それは今のところ少し遠いことです。)
また、通常の意味でのインターフェイスは必ずしもC#interface
である必要はなく、抽象クラスまたは通常のクラス(すべてのサブクラスがいくつかの共通コードを共有する必要がある場合に役立ちます)ただし、interface
で十分です)。
インターフェースがどのように役立つかを誰も平易な言葉で説明していないので、私はそれを試してみます(そしてShamimの答えから少し考えを盗みます)。
ピザ注文サービスのアイデアを考えてみましょう。複数のタイプのピザを持つことができ、各ピザの一般的なアクションはシステムで注文を準備することです。各ピザ 準備する必要があります しかし、各ピザ 別に準備されています。たとえば、ピザの詰め物を注文する場合、システムはおそらくレストランで特定の食材が利用可能であることを確認し、深皿のピザには必要ないものを脇に置いておく必要があります。
コードでこれを書くとき、技術的にはあなたがすることができます
public class Pizza()
{
public void Prepare(PizzaType tp)
{
switch (tp)
{
case PizzaType.StuffedCrust:
// prepare stuffed crust ingredients in system
break;
case PizzaType.DeepDish:
// prepare deep dish ingredients in system
break;
//.... etc.
}
}
}
ただし、深皿のピザ(C#の用語で)では、Prepare()
メソッドで設定するプロパティを詰めたクラストとは異なる必要がある場合があるため、多くのオプションプロパティがあり、クラスはうまくスケーリングしません(追加した場合新しいピザの種類)。
これを解決する適切な方法は、インターフェイスを使用することです。インターフェイスは、すべてのピザを準備できることを宣言しますが、各ピザは別々に準備できます。したがって、次のインターフェースがある場合:
public interface IPizza
{
void Prepare();
}
public class StuffedCrustPizza : IPizza
{
public void Prepare()
{
// Set settings in system for stuffed crust preparations
}
}
public class DeepDishPizza : IPizza
{
public void Prepare()
{
// Set settings in system for deep dish preparations
}
}
これで、注文処理コードは、材料を処理するために注文されたピザの種類を正確に知る必要がなくなりました。それはちょうど持っています:
public PreparePizzas(IList<IPizza> pizzas)
{
foreach (IPizza pizza in pizzas)
pizza.Prepare();
}
ピザの種類ごとに準備が異なりますが、コードのこの部分では、ピザの種類を気にする必要はありません。ピザ用に呼び出されることを知っているだけなので、Prepare
を呼び出すたびに、各ピザが自動的に準備されますコレクションに複数の種類のピザがある場合でも、その種類に基づいて正しく。
私にとって、これらのポイントは、コードを書くのをより簡単/より速くするものとしてそれらを見るのをやめるときだけ明らかになりました-これは彼らの目的ではありません。それらには多くの用途があります:
(これの使用法を視覚化するのは非常に簡単ではないため、これはピザの類推を失います)
画面上で簡単なゲームを作成しているとすると、あなたがやり取りするクリーチャーがいるでしょう。
A:フロントエンドとバックエンドの実装間に疎結合を導入することで、将来のコードの保守を容易にすることができます。
トロルしかいないので、これを最初から書くことができます:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
フロントエンド:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
2週間後、マーケティング担当者はOrcsも必要と判断します。彼らはTwitterでOrcsについて読んでいるので、次のようなことをしなければなりません。
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
フロントエンド:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
そして、これがどのように乱雑になり始めるかを見ることができます。ここでインターフェースを使用して、フロントエンドを1回作成して(ここで重要な部分を)テストし、必要に応じてさらにバックエンドアイテムをプラグインできます。
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
フロントエンドは次のとおりです。
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
フロントエンドは、インターフェイスICreatureのみを考慮します。トロールまたはオークの内部実装については気にしませんが、ICreatureを実装するという事実についてのみ気にします。
この観点からこれを見るときに注意すべき重要な点は、抽象的なクリーチャークラスも簡単に使用できたということです。この観点から、これは同じ効果があります。
そして、作成物を工場に抽出することができます:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
そして、フロントエンドは次のようになります。
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
フロントエンドは、TrollとOrcが実装されているライブラリへの参照さえ持っている必要はありません(ファクトリが別のライブラリにある場合)-それらについては何も知る必要はありません。
B:他の点では同種のデータ構造に一部のクリーチャーのみが持つ機能があるとします、たとえば.
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
フロントエンドは次のようになります。
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C:依存性注入の使用法
ほとんどの依存性注入フレームワークは、フロントエンドコードとバックエンド実装の間に非常に疎なカップリングがある場合に、作業しやすくなります。上記のファクトリーの例で、ファクトリーにインターフェースを実装させる場合:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
フロントエンドは、コンストラクター(通常)を介してこれを注入することができます(MVC APIコントローラーなど)。
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
DIフレームワーク(NinjectやAutofacなど)を使用すると、コンストラクターでICreatureFactoryが必要なときに実行時にCreatureFactoryのインスタンスが作成されるようにセットアップできます。これにより、コードがすてきでシンプルになります。
また、コントローラーの単体テストを作成するときに、モックされたICreatureFactoryを提供し(たとえば、具体的な実装にDBアクセスが必要な場合、単体テストをそれに依存させたくない)、コントローラーのコードを簡単にテストできることも意味します。
D:他の用途があります。たとえば、「レガシー」の理由で構造化されていない2つのプロジェクトAとBがあり、AにはBへの参照があります
次に、Aのメソッドを呼び出す必要があるBの機能を見つけます。循環参照を取得するため、具体的な実装を使用してそれを行うことはできません。
Aのクラスが実装するインターフェイスをBで宣言できます。具体的なオブジェクトがAの型である場合でも、Bのメソッドにインターフェイスを実装するクラスのインスタンスを問題なく渡すことができます。
以下に例を説明します。
public interface IFood // not Pizza
{
public void Prepare();
}
public class Pizza : IFood
{
public void Prepare() // Not order for explanations sake
{
//Prepare Pizza
}
}
public class Burger : IFood
{
public void Prepare()
{
//Prepare Burger
}
}
上記の例はあまり意味がありません。クラスを使用して上記のすべての例を実行できます(contractとしてのみ動作させる場合は、abstractクラス)。
public abstract class Food {
public abstract void Prepare();
}
public class Pizza : Food {
public override void Prepare() { /* Prepare pizza */ }
}
public class Burger : Food {
public override void Prepare() { /* Prepare Burger */ }
}
インターフェイスと同じ動作をします。 List<Food>
を作成し、どのクラスが一番上にあるかを知らずに繰り返します。
より適切な例は、多重継承です。
public abstract class MenuItem {
public string Name { get; set; }
public abstract void BringToTable();
}
// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
public override void BringToTable() { /* Bring soda to table */ }
}
// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
void Cook();
}
public class Pizza : MenuItem, IFood {
public override void BringToTable() { /* Bring pizza to table */ }
public void Cook() { /* Cook Pizza */ }
}
public class Burger : MenuItem, IFood {
public override void BringToTable() { /* Bring burger to table */ }
public void Cook() { /* Cook Burger */ }
}
その後、それらすべてをMenuItem
として使用でき、各メソッド呼び出しをどのように処理するかは気にしません。
public class Waiter {
public void TakeOrder(IEnumerable<MenuItem> order)
{
// Cook first
// (all except soda because soda is not IFood)
foreach (var food in order.OfType<IFood>())
food.Cook();
// Bring them all to the table
// (everything, including soda, pizza and burger because they're all menu items)
foreach (var menuItem in order)
menuItem.BringToTable();
}
}
アナロジー:だから、私は建設現場の監督です。
商人は常に工事現場を歩いています。誰がこれらのドアを通り抜けるのかわかりません。しかし、私は基本的に彼らに何をすべきかを伝えます。
上記のアプローチの問題は、私がしなければならないことです:(i)誰がそのドアを歩いているかを知り、それが誰であるかによって、私は彼らに何をすべきかを伝えなければなりません。つまり、特定の取引に関するすべてを知る必要があります。このアプローチに関連する費用/利益があります。
これは、大工のコードがBuildScaffolding()
からBuildScaffold()
に変更する場合(つまり、わずかな名前の変更)、呼び出し元クラス(つまりForeperson
クラス)も変更する必要があることを意味します-作成する必要がありますtwo(基本的に)1つではなくコードを変更します。ポリモーフィズムを使用すると、(基本的に)同じ結果を得るために必要な変更は1つだけです。
第二に、あなたは常に尋ねる必要はありません:あなたは誰ですか?わかりました...あなたは誰ですか?わかりました.....多相性-それはそのコードを乾燥させ、特定の状況で非常に効果的です:
多態性を使用すると、既存のコードを変更せずに、職人のクラスを簡単に追加できます。 (つまり、2番目のSOLID設計原則:オープン-クローズ原則)。
誰がドアを歩いても、「Work()」と言うことができ、彼らが専門とする敬意の仕事をするシナリオを想像してください。配管工はパイプを扱い、電気技師はワイヤーを扱います。
このアプローチの利点は、次のとおりです。(i)誰がそのドアを通って歩いているかを正確に知る必要はありません-知っている必要があるのは、彼らが一種のトレーディーになり、仕事ができるということです。 、(ii)その特定の取引について何も知る必要はありません。トレーディがそれを処理します。
したがって、これの代わりに:
If(electrician) then electrician.FixCablesAndElectricity()
if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks()
私はこのようなことをすることができます:
ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.
tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!
利点は、大工などの特定の職務要件が変更された場合、フォアパーソンは自分のコードを変更する必要がないことです-彼は知る必要も気にする必要もありません。重要なのは、大工がWork()の意味を知っていることだけです。第二に、新しいタイプの建設労働者が現場に来た場合、職長は貿易について何も知る必要はありません-職長が気にするのは、建設労働者(例えば、溶接機、ガラス屋、瓦職人など) Work()を完了させます。
インターフェイスを使用すると、自分が誰であるか、何ができるかを正確に把握していなくても、その人に割り当てられた作業を実行させることができます。これにより、既存のコードを変更せずに(貿易の)新しいタイプを簡単に追加できます(技術的にはごくわずかに変更するだけです)。これは、OOPアプローチとより機能的なアプローチの本当の利点です。プログラミング方法論。
上記のいずれかを理解していない場合、または明確でない場合は、コメントで質問してください。答えを改善しようとします。
Pythonで使用できる ダックタイピング がない場合、C#は抽象化を提供するためにインターフェイスに依存します。クラスの依存関係がすべて具象型である場合、他の型を渡すことはできません-インターフェイスを使用して、インターフェイスを実装する任意の型を渡すことができます。
ピザの例は、順序付けを処理する抽象クラスを使用する必要があり、ピザはピザの種類をオーバーライドするなどの理由で不適切です。
共有プロパティを持っているが、クラスが別の場所から継承している場合、または使用できる共通コードがない場合は、インターフェイスを使用します。たとえば、これは破棄できるIDisposable
を使用するものであり、破棄されることはわかっていますが、破棄されたときに何が起こるかわかりません。
インターフェースは、オブジェクトができること、期待するパラメーターと戻り値の型を伝える単なるコントラクトです。
基本クラスを制御または所有していない場合を考えてください。
たとえば、.NET for Winformsで視覚的なコントロールを使用します。これらはすべて、.NETフレームワークで完全に定義されている基本クラスControlから継承します。
カスタムコントロールを作成するビジネスをしていると仮定しましょう。新しいボタン、テキストボックス、リストビュー、グリッド、その他を作成し、それらすべてにコントロールのセットに固有の特定の機能を持たせたい場合。
たとえば、テーマを処理する一般的な方法、またはローカライズを処理する一般的な方法が必要な場合があります。
この場合、「基本クラスを作成する」ことはできません。その場合、コントロールに関連するすべてを再実装する必要があるためです。
代わりに、Button、TextBox、ListView、GridViewなどから派生して、コードを追加します。
しかし、これは問題を引き起こします。どのコントロールが「自分のもの」であるかを特定する方法、「私のフォーム上のすべてのコントロールについて、テーマをXに設定する」というコードを作成する方法.
インターフェイスを入力します。
インターフェースは、オブジェクトを見て、そのオブジェクトが特定の契約に準拠していることを確認する方法です。
「YourButton」を作成し、Buttonから派生させ、必要なすべてのインターフェイスのサポートを追加します。
これにより、次のようなコードを記述できます。
foreach (Control ctrl in Controls)
{
if (ctrl is IMyThemableControl)
((IMyThemableControl)ctrl).SetTheme(newTheme);
}
これはインターフェイスなしでは不可能であり、代わりに次のようなコードを記述する必要があります。
foreach (Control ctrl in Controls)
{
if (ctrl is MyThemableButton)
((MyThemableButton)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableTextBox)
((MyThemableTextBox)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableGridView)
((MyThemableGridView)ctrl).SetTheme(newTheme);
else ....
}
この場合、ピザの基本クラスを定義し、それらから継承するだけで(おそらくそうするでしょう)。ただし、インターフェイスを使用すると、他の方法では達成できないことを実行できる2つの理由があります。
クラスは複数のインターフェースを実装できます。クラスに必要な機能を定義するだけです。さまざまなインターフェースを実装するということは、クラスが異なる場所で複数の機能を果たすことができることを意味します。
インターフェイスは、クラスまたは呼び出し元よりもhogherスコープで定義できます。つまり、機能を分離し、プロジェクトの依存関係を分離し、1つのプロジェクトまたはクラスで機能を保持し、その実装を他の場所で実行できます。
2の意味の1つは、使用されているクラスを変更でき、適切なインターフェイスを実装するだけでよいということです。
C#で多重継承を使用できないことを考慮して、質問をもう一度見てください。
インターフェースは実際には実装クラスが従わなければならないコントラクトであり、実際、私が知っているほとんどすべての設計パターンのベースです。
あなたの例では、IS A Pizza(Pizzaインターフェースを実装することを意味する)が実装されていることが保証されているため、インターフェースが作成されます。
public void Order();
上記のコードの後に、次のようなものがあります。
public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
myPizza.order();
}
この方法でポリモーフィズムを使用していて、オブジェクトがorder()に応答することだけが重要です。
シェイプを描画するAPIに取り組んでいる場合、DirectXまたはグラフィックコール、またはOpenGLを使用できます。それで、私はあなたの呼び出しから私の実装を抽象化するインターフェースを作成します。
そのため、ファクトリメソッドMyInterface i = MyGraphics.getInstance()
を呼び出します。次に、コントラクトがあるので、MyInterface
で期待できる機能がわかります。そのため、i.drawRectangle
またはi.drawCube
を呼び出して、あるライブラリを別のライブラリに交換すると、その機能がサポートされることを知ることができます。
依存性注入を使用している場合、これはより重要になります。XMLファイルでは、実装をスワップアウトできます。
そのため、エクスポートできる暗号化ライブラリは一般的な用途向けであり、別の暗号化ライブラリはアメリカ企業のみに販売されている場合があります。違いは、設定ファイルを変更し、残りのプログラムはかわった。
これは.NETのコレクションで非常に多く使用されます。たとえば、List
変数を使用するだけで、ArrayListかLinkedListかを心配する必要はありません。
インターフェイスにコーディングする限り、開発者は実際の実装を変更でき、プログラムの残りの部分は変更されません。
インターフェイス全体をモックアウトできるため、単体テストの際にも便利です。したがって、データベースに移動する必要はありませんが、静的データを返すだけのモックアウト実装に移動するため、次のことを心配せずにメソッドをテストできます。メンテナンスのためにデータベースがダウンしているかどうか。
このページで「組成」という単語を検索しましたが、一度も見ませんでした。この答えは、前述の答えに加えて非常に多くあります。
オブジェクト指向プロジェクトでインターフェイスを使用する絶対的に重要な理由の1つは、継承よりも合成を優先できることです。インターフェースを実装することにより、実装に適用するさまざまなアルゴリズムから実装を切り離すことができます。
Derek Banasによるこの素晴らしい「Decorator Pattern」チュートリアル(おもしろいことにピザも例として使用しています)は価値のある例です:
インターフェースの最も重要な理由の1つであるDesign Patternsが含まれている投稿が多くないことに驚いています。コントラクトを使用することの全体像であり、マシンコードへの構文装飾ですが(正直なところ、コンパイラはおそらくそれらを単に無視します)、抽象化とインターフェイスはOOP、人間の理解、複雑なシステムアーキテクチャにとって極めて重要です。
ピザの例えを拡張して、本格的な3コースの食事をしましょう。すべての食品カテゴリのコアPrepare()
インターフェースは引き続き使用できますが、コース選択(スターター、メイン、デザート)の抽象宣言、および食品タイプ(savoury/sweet、vegetarian/non-vegetarian)の異なるプロパティもあります。 、グルテンフリーなど)。
これらの仕様に基づいて、Abstract Factoryパターンを実装してプロセス全体を概念化できますが、インターフェイスを使用して、基盤のみが具体的であることを確認できます。それ以外はすべて柔軟になり、ポリモーフィズムを促進できますが、Course
インターフェイスを実装するICourse
の異なるクラス間のカプセル化を維持できます。
もっと時間があれば、これの完全な例を作成するか、誰かがこれを拡張することができますが、要約すると、C#インターフェイスはこのタイプのシステムを設計するのに最適なツールです。
以下は、長方形のオブジェクトのインターフェースです。
interface IRectangular
{
Int32 Width();
Int32 Height();
}
必要なのは、オブジェクトの幅と高さにアクセスする方法を実装することだけです。
ここで、IRectangular
であるすべてのオブジェクトで機能するメソッドを定義しましょう。
static class Utils
{
public static Int32 Area(IRectangular rect)
{
return rect.Width() * rect.Height();
}
}
これは、長方形オブジェクトの面積を返します。
長方形のクラスSwimmingPool
を実装しましょう:
class SwimmingPool : IRectangular
{
int width;
int height;
public SwimmingPool(int w, int h)
{ width = w; height = h; }
public int Width() { return width; }
public int Height() { return height; }
}
また、別のクラスHouse
も長方形です:
class House : IRectangular
{
int width;
int height;
public House(int w, int h)
{ width = w; height = h; }
public int Width() { return width; }
public int Height() { return height; }
}
そのため、家やスイミングプールでArea
メソッドを呼び出すことができます。
var house = new House(2, 3);
var pool = new SwimmingPool(3, 4);
Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));
このようにして、クラスは任意の数のインターフェイスから動作(静的メソッド)を「継承」できます。
インターフェイスは、異なるクラス間の接続を適用するためのものです。たとえば、車のクラスとツリーがあります。
public class Car { ... }
public class Tree { ... }
両方のクラスに書き込み可能な機能を追加します。ただし、各クラスには独自の書き込み方法があります。だからあなたは単に作る;
public class Car : IBurnable
{
public void Burn() { ... }
}
public class Tree : IBurnable
{
public void Burn() { ... }
}
インターフェイスは、特定の機能のプロバイダーと対応するコンシューマーとの間のコントラクトを定義します。コントラクト(インターフェイス)から実装を切り離します。オブジェクト指向のアーキテクチャと設計を見てください。ウィキペディアから始めたい場合があります。 http://en.wikipedia.org/wiki/Interface_(computing)
あなたがそれらを必要とするとき、あなたはインターフェースを手に入れるでしょう:)あなたは例を学ぶことができますが、あなたはAhaが必要です!実際にそれらを取得する効果。
インターフェースが何であるかがわかったので、インターフェースなしでコーディングしてください。遅かれ早かれ、インターフェースの使用が最も自然なことになる問題に遭遇します。
ここにはたくさんの良い答えがありますが、少し違う視点から試してみたいと思います。
オブジェクト指向設計のSOLID原則に精通しているかもしれません。要約すれば:
S-単一責任の原則O-オープン/クローズドの原則L-リスコフ置換の原則I-インターフェース分離の原則D-依存関係の逆転の原則
SOLIDの原則に従うことで、クリーンで、適切にファクタリングされ、まとまりがあり、疎結合のコードを生成できます。とすれば:
「依存性管理は、あらゆる規模のソフトウェアにおける重要な課題です」(Donald Knuth)
依存関係管理に役立つものはすべて大きな勝利です。インターフェイスと依存関係の逆転の原則は、コードを具体的なクラスの依存関係から切り離すのに本当に役立つので、コードは、実装ではなくbehavioursの観点から記述および推論できます。これは、コンパイル時ではなく実行時に構成できるコンポーネントにコードを分割するのに役立ちます。また、残りのコードを変更することなく、これらのコンポーネントを非常に簡単にプラグインおよびプラグアウトできます。
インターフェイスは、特に、依存関係の反転の原理に役立ちます。コードでは、コードをサービスのコレクションにコンポーネント化でき、各サービスはインターフェイスで記述されます。サービスは、コンストラクターパラメーターとして渡すことにより、実行時にクラスに「注入」できます。この手法は、単体テストの作成を開始し、テスト駆動開発を使用する場合に非常に重要になります。それを試してみてください!インターフェイスを使用して、コードを個別に個別にテストできる管理可能なチャンクに分割する方法を簡単に理解できます。
インターフェイスについて考える最も簡単な方法は、継承の意味を認識することです。クラスCCがクラスCを継承する場合、以下の両方を意味します。
継承のこれら2つの機能は、ある意味では独立しています。継承は両方を同時に適用しますが、最初のなしで2番目を適用することもできます。オブジェクトが2つ以上の無関係なクラスからメンバーを継承できるようにすることは、1つのタイプのものを複数のタイプに置き換えることができるようにするよりもはるかに複雑なので、これは便利です。
インターフェースは抽象基本クラスに似ていますが、重要な違いがあります。基本クラスを継承するオブジェクトは、他のクラスを継承できません。対照的に、オブジェクトは、目的のクラスを継承したり、他のインターフェイスを実装したりする機能に影響を与えることなく、インターフェイスを実装できます。
これの優れた機能の1つ(.NETフレームワークで十分に活用されていないIMHO)は、オブジェクトが実行できることを宣言的に示すことができることです。たとえば、一部のオブジェクトは、データソースオブジェクトを必要としますが、そこから(リストで可能なように)インデックスによって物事を取得できますが、そこに何も保存する必要はありません。他のルーチンは、インデックスではなく(Collection.Addと同様に)ものを格納できるデータリポジトリオブジェクトを必要としますが、何も読み戻す必要はありません。一部のデータ型はインデックスによるアクセスを許可しますが、書き込みは許可しません。他のユーザーは書き込みを許可しますが、インデックスによるアクセスは許可しません。もちろん、いくつかは両方を許可します。
ReadableByIndexとAppendableが関連しない基底クラスである場合、ReadableByIndexを期待するものとAppendableを期待するものの両方に渡すことができる型を定義することは不可能です。 ReadableByIndexまたはAppendableをもう一方から派生させることにより、これを軽減しようとすることができます。派生クラスは、両方の目的でパブリックメンバーを使用可能にする必要がありますが、一部のパブリックメンバーは実際には機能しない可能性があることを警告します。 Microsoftのクラスとインターフェイスのいくつかはそれを行いますが、それはかなり厄介です。よりクリーンなアプローチは、さまざまな目的のためのインターフェースを持ち、オブジェクトに実際にできることのためのインターフェースを実装させることです。インターフェイスIReadableByIndexと別のインターフェイスIAppendableがある場合、どちらかを実行できるクラスは、実行できることに対して適切なインターフェイスを実装できます。
私にとって、インターフェースの利点/利点は、抽象クラスよりも柔軟であることです。継承できる抽象クラスは1つだけですが、複数のインターフェイスを実装できるため、多くの場所で抽象クラスを継承するシステムの変更が問題になります。 100箇所で継承される場合、変更には100箇所すべての変更が必要です。ただし、インターフェースを使用すると、新しいインターフェースに新しい変更を配置し、必要な場所でインターフェースを使用できます(SOLIDのインターフェースシーケンス)。さらに、インターフェイスの例のオブジェクトは、インターフェイスを実装する場所の数に関係なく、メモリ内で1回だけ使用されるため、メモリ使用量はインターフェイスの方が少ないようです。
Thereseは本当に素晴らしい例です。
もう1つは、switchステートメントの場合、rioが特定の方法でタスクを実行するたびに保守および切り替えを行う必要がなくなります。
ピザの例では、ピザを作りたい場合、必要なのはインターフェイスだけです。そこから各ピザが独自のロジックを処理します。
これにより、カップリングと循環的複雑さが軽減されます。それでもロジックを実装する必要がありますが、より広い視野で追跡する必要があるものは少なくなります。
各ピザについて、そのピザに固有の情報を追跡できます。他のピザだけが知る必要があるので、他のピザが何を持っているかは重要ではありません。
インターフェースの主な目的は、ユーザーと、そのインターフェースを実装する他のクラスとの間で契約を結び、コードを分離して拡張性を可能にすることです。
インターフェイスをデイジーチェーン接続して、さらに別のインターフェイスを作成することもできます。複数のインターフェイスを実装するこの機能により、開発者は、現在のクラス機能を変更せずにクラスに機能を追加できるという利点が得られます(SOLID Principles)
O =「クラスは拡張のために開かれ、修正のために閉じられる必要がある」