質問
誰かがインターフェースを分かりやすく説明してくれるか、またはいくつかの良い例を教えてくれませんか?あちこちでインターフェースがポップアップ表示されるのを目にしますが、インターフェースやそれをいつ使用するかについての適切な説明に実際に触れたことはありません。
私はインターフェースとインターフェースという文脈でインターフェースについて話しています。抽象クラス。
解決
インターフェイスを使用すると、型ではなく「説明」に基づいてプログラミングできるため、ソフトウェアの要素をより緩やかに関連付けることができます。
次のように考えてください。隣のキューブにいる誰かとデータを共有したいので、フラッシュ スティックを取り出してコピー/ペーストします。あなたは隣を歩き、男は「そのUSBですか?」と言います。そして、あなたは「はい」と言います - すべてセット。フラッシュ スティックのサイズやメーカーは関係ありません。重要なのは、USB であることだけです。
同様に、インターフェイスを使用すると開発を一般化できます。別のたとえを使用すると、仮想的に車にペイントするアプリケーションを作成したいと考えたとします。次のような署名があるかもしれません。
public void Paint(Car car, System.Drawing.Color color)...
これは、クライアントが「今度はトラックを塗装したい」と言うまでは機能するため、次のようにすることができます。
public void Paint (Vehicle vehicle, System.Drawing.Color color)...
これによりアプリの幅が広がります...あなたのクライアントが「今私は家を描きたい!」と言うまで最初からできたことは、インターフェイスを作成することです。
public interface IPaintable{
void Paint(System.Drawing.Color color);
}
...そしてそれをルーチンに渡します。
public void Paint(IPaintable item, System.Drawing.Color color){
item.Paint(color);
}
これが理にかなっていれば幸いです。かなり単純化した説明ですが、核心を理解できれば幸いです。
他のヒント
インターフェイスは、クラスとそれを呼び出すコードとの間の契約を確立します。また、同じインターフェイスを実装しながら異なるアクションやイベントを実行する類似のクラスを作成することもでき、実際にどれを操作しているのかを知る必要がありません。例としてはこちらの方が分かりやすいかもしれないので、ここで試してみましょう。
Dog、Cat、Mouse というクラスがいくつかあるとします。これらのクラスはそれぞれ Pet であり、理論的にはそれらをすべて Pet と呼ばれる別のクラスから継承できますが、ここに問題があります。ペット自体は何もしません。お店に行ってペットを買うことはできません。犬や猫を買いに行くことはできますが、ペットは抽象的な概念であり、具体的なものではありません。
ペットが特定のことを行うことができることはご存知でしょう。彼らは寝たり、食べたりすることができます。そこで、IPet というインターフェイスを定義すると、次のようになります (C# 構文)
public interface IPet
{
void Eat(object food);
void Sleep(int duration);
}
Dog、Cat、および Mouse の各クラスは IPet を実装します。
public class Dog : IPet
したがって、これらの各クラスには、Eat と Sleep の独自の実装が必要になります。やあ、契約してるんだね…さて、要点は何ですか。
次に、PetStore という新しいオブジェクトを作成するとします。そして、これはあまり良いペットストアではないので、基本的にはランダムなペットを販売するだけです(はい、これが不自然な例であることはわかっています)。
public class PetStore
{
public static IPet GetRandomPet()
{
//Code to return a random Dog, Cat, or Mouse
}
}
IPet myNewRandomPet = PetStore.GetRandomPet();
myNewRandomPet.Sleep(10);
問題は、それがどんな種類のペットになるかわからないことです。インターフェイスのおかげで、何を食べて寝るかがわかります。
したがって、この答えはまったく役に立たなかったかもしれませんが、一般的な考え方は、インターフェイスを使用すると、オブジェクトを取得できる依存関係の挿入や制御の反転などのきちんとしたことを実行でき、オブジェクトが本当に実行することなく実行できる明確に定義されたリストを持つことができます。そのオブジェクトの具体的なタイプが何であるかを知ること。
最も簡単な答えは、クラスが実行できることはインターフェイスによって定義されるということです。これは、クラスがそのアクションを実行できるようにするという「契約」です。
Public Interface IRollOver
Sub RollOver()
End Interface
Public Class Dog Implements IRollOver
Public Sub RollOver() Implements IRollOver.RollOver
Console.WriteLine("Rolling Over!")
End Sub
End Class
Public Sub Main()
Dim d as New Dog()
Dim ro as IRollOver = TryCast(d, IRollOver)
If ro isNot Nothing Then
ro.RollOver()
End If
End Sub
基本的に、Dog クラスがそのインターフェイスを実装し続ける限り、常にロールオーバーできることが保証されます。猫が RollOver() を実行できるようになった場合、猫もそのインターフェイスを実装できるため、RollOver() を要求するときに犬と猫の両方を同等に扱うことができます。
友人の車を運転するとき、あなたは多かれ少なかれその方法を知っています。これは、従来の自動車はすべて非常に似たインターフェイスを備えているためです。ハンドル、ペダルなど。このインターフェイスは、自動車メーカーとドライバーの間の契約であると考えてください。ドライバー (ソフトウェア用語ではインターフェイスのユーザー/クライアント) として、さまざまな車を運転するためにその詳細を学ぶ必要はありません。たとえば、ハンドルを回すと車が曲がるということだけを知っておく必要があります。自動車メーカー (ソフトウェア用語でのインターフェイスの実装プロバイダー) として、ドライバーが追加のトレーニングなしで新しい車を使用できるように、新しい車が何を備え、どのように動作するべきかを明確に理解しています。この契約は、ソフトウェア設計の人々が (ユーザーとプロバイダーの) デカップリングと呼ぶものです。クライアント コードは、その特定の実装ではなくインターフェイスの使用に関するものであるため、オブジェクトの詳細を知る必要はありません。インターフェイスの実装。
インターフェイスは、システムの異なる、場合によっては異種の部分間の結合を軽減するメカニズムです。
から 。ネット 視点
- インターフェイス定義は、操作やプロパティのリストです。
- インターフェイス メソッドは常にパブリックです。
- インターフェイス自体はパブリックである必要はありません。
クラスを作成すると、 実装する インターフェイスに定義されているすべてのメソッドとプロパティの明示的または暗黙的な実装を提供する必要があります。
さらに、.NET には単一の継承しかなく、オブジェクトがそのクラス階層を認識していない、またはそのクラス階層の外側にある他のオブジェクトにメソッドを公開するには、インターフェイスが必要です。これは暴露行為とも呼ばれます。
もう少し具体的な例:
最後に更新したのは誰なのか、いつ更新したのかのプロパティを持つ DTO (データ転送オブジェクト) が多数あることを考えてみましょう。問題は、このプロパティが常に関連しているわけではないため、すべての DTO がこのプロパティを持っているわけではないことです。
同時に、ワークフローに送信されたときにこれらのプロパティが使用可能な場合に設定されることを保証する汎用メカニズムが必要ですが、ワークフロー オブジェクトは送信されたオブジェクトから疎結合である必要があります。つまり送信ワークフロー メソッドは、実際には各オブジェクトの詳細をすべて知っている必要はなく、ワークフロー内のすべてのオブジェクトが必ずしも DTO オブジェクトである必要はありません。
// First pass - not maintainable
void SubmitToWorkflow(object o, User u)
{
if (o is StreetMap)
{
var map = (StreetMap)o;
map.LastUpdated = DateTime.UtcNow;
map.UpdatedByUser = u.UserID;
}
else if (o is Person)
{
var person = (Person)o;
person.LastUpdated = DateTime.Now; // Whoops .. should be UtcNow
person.UpdatedByUser = u.UserID;
}
// Whoa - very unmaintainable.
上記のコードでは、 SubmitToWorkflow()
あらゆるオブジェクトについて知らなければなりません。さらに、コードは 1 つの大規模な if/else/switch で混乱しており、 同じことを繰り返さないでください (DRY) 原則に基づいており、開発者は新しいオブジェクトがシステムに追加されるたびに変更をコピー/ペーストすることを覚えておく必要があります。
// Second pass - brittle
void SubmitToWorkflow(object o, User u)
{
if (o is DTOBase)
{
DTOBase dto = (DTOBase)o;
dto.LastUpdated = DateTime.UtcNow;
dto.UpdatedByUser = u.UserID;
}
若干良くなりましたが、まだ脆いです。他のタイプのオブジェクトを送信したい場合は、さらに case ステートメントが必要になります。等
// Third pass pass - also brittle
void SubmitToWorkflow(DTOBase dto, User u)
{
dto.LastUpdated = DateTime.UtcNow;
dto.UpdatedByUser = u.UserID;
これはまだ脆弱であり、どちらの方法も、すべての DTO がこのプロパティを実装しなければならないという制約を課しますが、これは普遍的に適用できるものではないと示されました。開発者の中には何もしないメソッドを書きたくなる人もいるかもしれませんが、それは臭いです。更新追跡をサポートしているように見せかけて、実際にはサポートしていないクラスは望ましくありません。
インターフェースはどのように役立ちますか?
非常に単純なインターフェイスを定義すると、次のようになります。
public interface IUpdateTracked
{
DateTime LastUpdated { get; set; }
int UpdatedByUser { get; set; }
}
この自動更新追跡を必要とするクラスはインターフェイスを実装できます。
public class SomeDTO : IUpdateTracked
{
// IUpdateTracked implementation as well as other methods for SomeDTO
}
ワークフロー メソッドは、より汎用的で小さく、保守しやすいものにすることができます。また、ワークフロー メソッドはインターフェイスのみを処理するため、インターフェイス (DTO など) を実装するクラスの数に関係なく機能し続けます。
void SubmitToWorkflow(object o, User u)
{
IUpdateTracked updateTracked = o as IUpdateTracked;
if (updateTracked != null)
{
updateTracked.LastUpdated = DateTime.UtcNow;
updateTracked.UpdatedByUser = u.UserID;
}
// ...
- 変化に注目することができます
void SubmitToWorkflow(IUpdateTracked updateTracked, User u)
型の安全性は保証されますが、このような状況ではあまり意味がないようです。
私たちが使用する一部の実稼働コードでは、データベース定義からこれらの DTO クラスを作成するためのコード生成が行われています。開発者が行う必要があるのは、フィールド名を正しく作成し、クラスをインターフェイスで修飾することだけです。プロパティが LastUpdated および UpdatedByUser である限り、機能します。
たぶんあなたは尋ねています データベースがレガシーでそれが不可能な場合はどうなりますか? もう少し入力する必要があります。インターフェイスのもう 1 つの優れた機能は、クラス間のブリッジを作成できることです。
以下のコードでは、架空の LegacyDTO
, 、同様の名前のフィールドを持つ既存のオブジェクト。これは、既存の異なる名前のプロパティをブリッジするために IUpdateTracked インターフェイスを実装しています。
// Using an interface to bridge properties
public class LegacyDTO : IUpdateTracked
{
public int LegacyUserID { get; set; }
public DateTime LastSaved { get; set; }
public int UpdatedByUser
{
get { return LegacyUserID; }
set { LegacyUserID = value; }
}
public DateTime LastUpdated
{
get { return LastSaved; }
set { LastSaved = value; }
}
}
あなたはそうかも知れません 素晴らしいですが、複数のプロパティがあると混乱しませんか? または これらのプロパティがすでに存在するが、それらが何か別の意味を持っている場合はどうなるでしょうか? .NET では、インターフェイスを明示的に実装できます。
これが意味するのは、IUpdateTracked プロパティは、IUpdateTracked への参照を使用している場合にのみ表示されるということです。宣言に public 修飾子がなく、宣言にインターフェイス名が含まれていることに注意してください。
// Explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
int IUpdatable.UpdatedByUser
{ ... }
DateTime IUpdatable.LastUpdated
{ ... }
クラスがインターフェイスを実装する方法を非常に柔軟に定義できるため、開発者はオブジェクトをそれを使用するメソッドから自由に切り離すことができます。インターフェイスは結合を解消する優れた方法です。
インターフェースにはこれ以外にもたくさんの機能があります。これは、インターフェイス ベースのプログラミングの一側面を利用した単純化された実際の例にすぎません。
前に述べたように、また他のレスポンダーでも述べたように、特定のクラス参照ではなくインターフェイス参照を受け取ったり返したりするメソッドを作成できます。リスト内の重複を見つける必要がある場合は、 IList
(リストで動作する操作を定義するインターフェイス)、具体的なコレクション クラスに制約されません。
// Decouples the caller and the code as both
// operate only on IList, and are free to swap
// out the concrete collection.
public IList<T> FindDuplicates( IList<T> list )
{
var duplicates = new List<T>()
// TODO - write some code to detect duplicate items
return duplicates;
}
バージョン管理に関する注意事項
パブリックインターフェースの場合は、次のように宣言します。 インターフェイス x は次のようになることを保証します。 また、コードを出荷してインターフェースを公開したら、それを決して変更しないでください。コンシューマ コードがそのインターフェイスに依存し始めるとすぐに、現場でコードを壊したくなくなります。
見る このハッキングされた投稿 良い議論のために。
インターフェイスと抽象 (基本) クラス
抽象クラスは実装を提供できますが、インターフェイスは実装できません。NVPI (非仮想パブリック インターフェイス) パターンなどのガイドラインに従う場合、抽象クラスはバージョン管理の面でいくつかの点でより柔軟になります。
.NET では、クラスは 1 つのクラスからしか継承できませんが、クラスは必要な数のインターフェイスを実装できることを繰り返しておきます。
依存関係の注入
インターフェイスと依存関係注入 (DI) について簡単に要約すると、インターフェイスを使用すると、開発者はインターフェイスに対してプログラムされたコードを記述してサービスを提供できるようになります。実際には、多数の小さなインターフェイスや小さなクラスが作成される可能性があります。1 つの考えとして、1 つのことを 1 つだけ実行する小さなクラスの方がコーディングと保守がはるかに簡単です。
class AnnualRaiseAdjuster
: ISalaryAdjuster
{
AnnualRaiseAdjuster(IPayGradeDetermination payGradeDetermination) { ... }
void AdjustSalary(Staff s)
{
var payGrade = payGradeDetermination.Determine(s);
s.Salary = s.Salary * 1.01 + payGrade.Bonus;
}
}
簡単に言うと、上記のスニペットに示されている利点は、給与等級の決定が年次昇給調整機能に注入されるだけであるということです。給与等級がどのように決定されるかは、このクラスにとっては実際には重要ではありません。テスト時に、開発者は給与等級決定結果を模擬して、給与調整機能が期待どおりに機能することを確認できます。また、テストはクラスのみをテストし、他のすべてをテストするわけではないため、テストも高速です。
ただし、このテーマに特化した本が何冊もあるため、これは DI の入門書ではありません。上の例は非常に単純化されています。
これはかなり「長い」テーマですが、簡単に言ってみましょう。
インターフェイスは、「その名の通り」コントラクトです。でも、その言葉は忘れてください。
これらを理解する最良の方法は、ある種の疑似コードの例を使用することです。私はずっと前から彼らをそのように理解していました。
メッセージを処理するアプリがあるとします。メッセージには、件名、テキストなどのいくつかの要素が含まれます。
したがって、データベースを読み取り、メッセージを抽出するための MessageController を作成します。FAX も間もなく実装されると突然聞くまでは、とてもうれしいです。したがって、「FAX」を読み取り、メッセージとして処理する必要があります。
これは簡単にスパゲッティ コードになる可能性があります。したがって、「メッセージ」のみを制御する MessageController を使用する代わりに、それを操作できるようにします。 インターフェース IMessage と呼ばれます (I は一般的な使用法ですが、必須ではありません)。
IMessage インターフェイスには、いくつかの基本データが含まれています。 必要 メッセージをそのまま処理できることを確認します。
したがって、EMail、Fax、PhoneCall クラスを作成するときは、 埋め込む の インターフェース 呼ばれた Iメッセージ.
したがって、MessageController では、次のようにメソッドを呼び出すことができます。
private void ProcessMessage(IMessage oneMessage)
{
DoSomething();
}
インターフェイスを使用したことがない場合は、次のものを用意する必要があります。
private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.
したがって、を使用することで、 一般 インターフェイスの場合、FAX、電子メール、電話などに関係なく、ProcessMessage メソッドがそのインターフェイスで動作できることを確認しました。
なぜ、どのようにして?
なぜならインターフェースは 契約 それはあなたがいくつかのことを指定します しなければならない それを使用できるようにするには、遵守(または実装)してください。と考えてください。 バッジ. 。オブジェクト「Fax」に IMessage インターフェイスがない場合、ProcessMessage メソッドはそれを処理できません。IMessage を期待するメソッドに FAX を渡しているため、無効な型が与えられます。物体。
要点はわかりますか?
インターフェイスは、使用できるメソッドとプロパティの「サブセット」であると考えてください。 にもかかわらず 実際のオブジェクトのタイプ。元のオブジェクト (Fax、電子メール、PhoneCall など) がそのインターフェイスを実装している場合、そのインターフェイスを必要とするメソッド間で安全にそれを渡すことができます。
そこにはさらに魔法が隠されており、インターフェイスを元のオブジェクトにキャストして戻すことができます。
ファックス myFax = (ファックス)SomeIMessageThatIReceive;
.NET 1.1 の ArrayList() には、IList と呼ばれる優れたインターフェイスがありました。IList (非常に「汎用」) がある場合は、それを ArrayList に変換できます。
ArrayList ar = (ArrayList)SomeIList;
そして、何千ものサンプルが世に出ています。
ISortable、IComparable などのインターフェイスは、必要なメソッドとプロパティを定義します。 しなければならない その機能を実現するには、クラスに実装します。
サンプルを拡張するには、タイプが IMessage の場合、電子メール、FAX、PhoneCall の List<> をすべて同じリスト内に含めることができますが、オブジェクトが単に電子メール、FAX などの場合、これらをすべて一緒に含めることはできません。 。
オブジェクトを並べ替える (または列挙するなど) 場合は、オブジェクトに対応するインターフェイスを実装する必要があります。.NET サンプルでは、「Fax」オブジェクトのリストがあり、次のことを実行できるようにしたい場合、 選別 MyList.Sort() を使用することで、 必要 FAX クラスを次のように作成します。
public class Fax : ISorteable
{
//implement the ISorteable stuff here.
}
これがヒントになれば幸いです。他のユーザーが他の良い例を投稿する可能性があります。幸運を!インターフェイスの力を活用してください。
警告:インターフェイスに関してすべてが良いわけではありません。 いくつかの 彼らの問題を解決するには、OOP 純粋主義者がこれに対して戦争を始めるでしょう。私は脇に留まります。Interfce (少なくとも .NET 2.0 では) の欠点の 1 つは、PRIVATE メンバーを持てない、つまり保護できないことです。 しなければならない 公になること。これはある程度理にかなっていますが、単にプライベートまたはプロテクトとして宣言したい場合もあります。
プログラミング言語内にある関数インターフェイスに加えて、設計アイデアを他の人に表現する際の強力なセマンティック ツールとしても機能します。 人々.
適切に設計されたインターフェイスを備えたコードベースは、突然議論しやすくなります。「はい、新しいリモートサーバーを登録するには、資格情報マネージャーが必要です。」 「プロパティマップを渡して、作業インスタンスを取得するために物事に渡します。」
複雑なことを単一の単語で対処できる能力は非常に便利です。
インターフェイスを使用すると、オブジェクトに対して一般的な方法でコーディングできます。たとえば、レポートを送信するメソッドがあるとします。ここで、新しいレポートを作成する必要がある新しい要件があるとします。すでに書いたメソッドを再利用できればいいですよね?インターフェースを使用すると、それが簡単になります。
interface IReport
{
string RenderReport();
}
class MyNewReport : IReport
{
public string RenderReport()
{
return "Hello World Report!";
}
}
class AnotherReport : IReport
{
public string RenderReport()
{
return "Another Report!";
}
}
//This class can process any report that implements IReport!
class ReportEmailer()
{
public void EmailReport(IReport report)
{
Email(report.RenderReport());
}
}
class MyApp()
{
void Main()
{
//create specific "MyNewReport" report using interface
IReport newReport = new MyNewReport();
//create specific "AnotherReport" report using interface
IReport anotherReport = new AnotherReport();
ReportEmailer reportEmailer = new ReportEmailer();
//emailer expects interface
reportEmailer.EmailReport(newReport);
reportEmailer.EmailReport(anotherReport);
}
}
インターフェイスは、「OOD の 3 つの柱」の 1 つであるポリモーフィズムの鍵でもあります。
上で触れた人もいますが、ポリモーフィズムとは、特定のクラスがさまざまな「形式」を取ることができることを意味します。つまり、「Dog」と「Cat」という 2 つのクラスがあり、両方ともインターフェイス「INeedFreshFoodAndWater」を実装している場合 (へへ)、コードは次のようなことを実行できます (疑似コード)。
INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());
foreach(INeedFreshFoodAndWater item in array)
{
item.Feed();
item.Water();
}
これは、オブジェクトのさまざまなクラスを抽象的に扱うことができ、オブジェクトをより疎結合にするなどのことができるため、強力です。
さて、抽象クラスと抽象クラスについての話です。インターフェース...
概念的には、抽象クラスは基本クラスとして使用されます。多くの場合、サブクラス自体はすでにいくつかの基本機能を提供しており、サブクラスは抽象メソッド (抽象基本クラスに実装されていないメソッド) の独自の実装を提供する必要があります。
インターフェイスは主に、クライアント コードを特定の実装の詳細から切り離すために使用されます。また、クライアント コードを変更せずに実装を切り替えることができるため、クライアント コードがより汎用的になる場合もあります。
技術レベルでは、一部の言語 (C++ など) では構文上の違いがないため、または分離や一般化の目的で抽象クラスを使用することもできるため、抽象クラスとインターフェイスの間に線を引くのは難しくなります。定義上、すべての基本クラスは、そのすべてのサブクラスが尊重するインターフェイスを定義しているため、抽象クラスをインターフェイスとして使用することが可能です (つまり、基本クラスの代わりにサブクラスを使用できる必要があります)。
インターフェイスは、継承を使用せずにオブジェクトが一定量の機能を実装することを強制する方法です (これにより、インターフェイスを使用して実現できる疎結合ではなく、強結合のコードが生成されます)。
インターフェイスは実装ではなく機能を説明します。
目にするインターフェイスのほとんどは、メソッドとプロパティのシグネチャのコレクションです。インターフェイスを実装する人は、インターフェイス内のすべてのものに対して定義を提供する必要があります。
簡単に言えば:インターフェイスはメソッドによって定義されたクラスですが、メソッドには実装がありません。対照的に、抽象クラスには一部のメソッドが実装されていますが、すべてではありません。
インターフェースをコントラクトとして考えてください。クラスがインターフェイスを実装すると、本質的にはその契約の条項を尊重することに同意したことになります。消費者は、自分が所有するオブジェクトが契約上の義務を遂行できるかどうかだけを気にします。内部の仕組みや詳細は重要ではありません。
インターフェイスを使用する十分な理由の 1 つは、Java の抽象クラスとは、サブクラスは複数の基本クラスを拡張できませんが、複数のインターフェイスを実装できることを意味します。
Java では多重継承は許可されていません (非常に正当な理由があるため、恐ろしいダイヤモンドを調べてください) が、クラスに複数の動作セットを提供させたい場合はどうすればよいでしょうか?これを使用する人全員に、それがシリアル化できること、また、画面上に自分自身をペイントできることを知らせたいとします。答えは、2 つの異なるインターフェイスを実装することです。
インターフェイスには独自の実装やインスタンス メンバーが含まれていないため、それらのいくつかを曖昧さなく同じクラスに安全に実装できます。
欠点は、各クラスで個別に実装する必要があることです。したがって、階層が単純で、継承するすべてのクラスで同じである必要がある実装の部分がある場合は、抽象クラスを使用します。
静的型付きオブジェクト指向言語のインターフェイスを参照していると仮定すると、主な用途は、クラスが特定の規約またはプロトコルに従っていることを表明することです。
次のものを持っているとします。
public interface ICommand
{
void Execute();
}
public class PrintSomething : ICommand
{
OutputStream Stream { get; set; }
String Content {get; set;}
void Execute()
{
Stream.Write(content);
}
}
これで、代替可能なコマンド構造が完成しました。IExecute を実装するクラスのインスタンスは、ある種のリスト (たとえば、IEnumerable を実装するもの) に格納でき、それをループして各オブジェクトを実行することができ、各オブジェクトが正しいことを実行することがわかります。実行する独自のコマンド リストを持つ CompositeCommand を実装することによって複合コマンドを作成するか、一連のコマンドを繰り返し実行する LoopingCommand を実装すると、ほとんどの単純なインタープリターが完成します。
一連のオブジェクトを、それらすべてに共通する動作に落とし込むことができれば、インターフェイスを抽出する必要がある可能性があります。また、場合によっては、インターフェイスを使用して、オブジェクトがそのクラスの関心事に誤って侵入するのを防ぐことができます。たとえば、クライアントがオブジェクト内のデータを変更するのではなく、取得のみを許可するインターフェースを実装し、ほとんどのオブジェクトが取得インターフェースへの参照のみを受け取るようにすることができます。
インターフェイスが比較的単純で、前提条件がほとんどない場合に、インターフェイスは最も効果的に機能します。
これをより理解するには、リスコフ置換原理を調べてください。
C++ などの一部の静的型付け言語は、第一級の概念としてインターフェイスをサポートしていないため、純粋な抽象クラスを使用してインターフェイスを作成します。
アップデートあなたは抽象クラスと抽象クラスについて質問しているようですので、インターフェースについては、私の好みの簡略化を以下に示します。
- インターフェイスは機能と機能を定義します。
- 抽象クラスはコア機能を定義します。
通常、抽象クラスを構築する前に、インターフェイスの抽出リファクタリングを実行します。作成上の契約 (具体的には、特定のタイプのコンストラクターが常にサブクラスによってサポートされるべきであること) が必要であると考える場合、抽象クラスを構築する可能性が高くなります。ただし、C#/Java で「純粋な」抽象クラスを使用することはほとんどありません。私は、意味のある動作を含むメソッドを少なくとも 1 つ含むクラスを実装し、そのメソッドによって呼び出されるテンプレート メソッドをサポートするために抽象メソッドを使用する可能性が高くなります。この場合、抽象クラスは動作の基本実装であり、すべての具象サブクラスが再実装することなく利用できます。
簡単な答え:インターフェイスは、メソッド シグネチャ (+ 戻り値の型) の集まりです。物体がそれを言うとき 実装する インターフェースは、そのメソッドのセットを公開していることがわかります。
インターフェイスは、厳密に型指定され、ポリモーフィックな方法で規約を実装する方法です。
現実世界の良い例は、.NET の IDisposable です。IDisposable インターフェイスを実装するクラスは、そのクラスに Dispose() メソッドを強制的に実装します。クラスが Dispose() を実装していない場合、ビルドしようとするとコンパイラ エラーが発生します。さらに、このコード パターンは次のとおりです。
using (DisposableClass myClass = new DisposableClass())
{
// code goes here
}
実行が内部ブロックを出るときに myClass.Dispose() が自動的に実行されます。
ただし、これが重要ですが、Dispose() メソッドが何をすべきかについての強制はありません。Dispose() メソッドでファイルからランダムなレシピを選択し、配布リストに電子メールで送信することもできますが、コンパイラは気にしません。IDisposable パターンの目的は、リソースのクリーンアップを簡単にすることです。クラスのインスタンスがファイル ハンドルを保持する場合、IDisposable を使用すると、割り当て解除とクリーンアップ コードを 1 か所に集中させ、割り当て解除が常に行われるようにする使用スタイルを促進することが非常に簡単になります。
そしてそれがインターフェースの鍵です。これらは、プログラミング規則と設計パターンを合理化する方法です。これを正しく使用すると、より使いやすく、保守しやすく、より正確な、よりシンプルで自己文書化されたコードが促進されます。
これは私がよく使うDB関連の例です。リストのようなオブジェクトとコンテナ オブジェクトがあるとします。オブジェクトを特定の順序で保存したい場合があると仮定します。シーケンスは配列内の位置に関連しているのではなく、オブジェクトがより大きなオブジェクト セットのサブセットであり、シーケンスの位置がデータベース SQL フィルタリングに関連していると仮定します。
カスタマイズしたシーケンスの位置を追跡するには、オブジェクトにカスタム インターフェイスを実装させることができます。カスタム インターフェイスは、そのようなシーケンスを維持するために必要な組織的な作業を仲介できます。
たとえば、関心のあるシーケンスはレコード内の主キーとは何の関係もありません。インターフェイスを実装するオブジェクトでは、myObject.next() または myObject.prev() と言えます。
私もあなたと同じ問題を抱えていて、「契約」の説明が少しわかりにくいと思います。
メソッドが IEnumerable インターフェイスを内部パラメータとして受け取ることを指定する場合、これはパラメータが IEnumerable インターフェイスから継承する型である必要があり、したがって IEnumerable インターフェイスで指定されたすべてのメソッドをサポートすることを指定する契約であると言えます。ただし、抽象クラスまたは通常のクラスを使用した場合も同じことが当てはまります。これらのクラスを継承するオブジェクトはパラメータとして渡しても問題ありません。いずれの場合でも、継承されたオブジェクトは、基底クラスが通常クラス、抽象クラス、インターフェイスのいずれであっても、基底クラス内のすべてのパブリック メソッドをサポートすると言えます。
すべての抽象メソッドを備えた抽象クラスは基本的にインターフェイスと同じであるため、インターフェイスはメソッドが実装されていない単なるクラスであると言えます。実際には、言語からインターフェイスを削除し、代わりに抽象メソッドのみを持つ抽象クラスを使用することもできます。それらを分離する理由はセマンティック上の理由によるものだと思いますが、コーディング上の理由はわかりませんし、混乱を招くだけだと思います。
もう 1 つの提案は、インターフェイスはクラスの別のバリエーションであるため、インターフェイスの名前をインターフェイス クラスに変更することです。
特定の言語では、クラスが 1 つのクラスだけを継承して複数のインターフェイスを継承できるという微妙な違いがありますが、他の言語では両方のインターフェイスを多く持つことができますが、それは別の問題であり、直接関係はないと思います
インターフェイスを理解する最も簡単な方法は、クラスの継承が何を意味するのかを考えることから始めることです。これには次の 2 つの側面が含まれます。
- 派生クラスのメンバーは、基本クラスのパブリック メンバーまたは保護されたメンバーを独自のメンバーとして使用できます。
- 派生クラスのメンバーは、基本クラスのメンバー (置換可能であることを意味します) を期待するコードで使用できます。
これらの機能は両方とも便利ですが、クラスが複数のクラスのメンバーを独自のクラスとして使用できるようにするのは難しいため、多くの言語とフレームワークでは、クラスが単一の基本クラスから継承することしか許可されていません。一方、クラスを他の複数の無関係なものと置き換えることに特別な困難はありません。
さらに、継承の最初の利点は主にカプセル化によって達成できるため、最初のタイプの多重継承を許可することによる相対的な利点はある程度限定されます。一方、オブジェクトを複数の無関係なタイプのものに置き換えることができることは、言語サポートなしでは簡単に実現できない便利な機能です。
インターフェイスは、言語/フレームワークが、プログラムが最初の側面を提供する必要なく、複数の基本型の継承の 2 番目の側面から恩恵を受けることを可能にする手段を提供します。
インターフェイスは完全な抽象クラスのようなものです。つまり、抽象メンバーのみを持つ抽象クラスです。複数の完全に抽象クラスを継承するような、複数のインターフェイスを実装することもできます。ともかく..この説明は、抽象クラスが何であるかを理解している場合にのみ役に立ちます。
ここで他の人が言ったように、インターフェイスはコントラクト(インターフェイスを使用するクラスがどのように「見える」か)を定義し、抽象クラスは共有機能を定義します。
コードが役に立つかどうか見てみましょう:
public interface IReport
{
void RenderReport(); // This just defines the method prototype
}
public abstract class Reporter
{
protected void DoSomething()
{
// This method is the same for every class that inherits from this class
}
}
public class ReportViolators : Reporter, IReport
{
public void RenderReport()
{
// Some kind of implementation specific to this class
}
}
public class ClientApp
{
var violatorsReport = new ReportViolators();
// The interface method
violatorsReport.RenderReport();
// The abstract class method
violatorsReport.DoSomething();
}
インターフェイスには、インターフェイス内で定義されたメソッドを含めるために、インターフェイスを実装するクラスが必要です。
その目的は、クラス内のコードを見なくても、そのコードが特定のタスクに使用できるかどうかを知ることができるようにすることです。たとえば、Java の Integer クラスは同等のインターフェイスを実装しているため、メソッド ヘッダー (パブリック クラス String が Comparable を実装している) だけを見れば、それに CompareTo() メソッドが含まれていることがわかります。
単純なケースでは、以下を実装する共通の基本クラスを使用して、インターフェイスで得られるものと同様のものを実現できます。 show()
(あるいはおそらくそれを抽象的なものとして定義します)。一般的な名前をもっと具体的なものに変更させてください。 鷲 そして 鷹 の代わりに MyClass1 そして MyClass2. 。その場合、次のようなコードを書くことができます
Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);
これにより、あらゆるものを処理できるコードを書くことができます。 です 鳥. 。その後、次のようなコードを作成できます。 鳥 インスタンスとして扱うインスタンスに作用すること (飛ぶ、食べる、卵を産むなど) を実行します。 鳥. 。そのコードは次のいずれかにかかわらず機能します 鳥 本当にです 鷲, 鷹, 、またはそこから派生したその他のもの 鳥.
しかし、本当の意味を持っていない場合、そのパラダイムは混乱し始めます。 です 関係。空を飛び回るコードを書きたいとします。を受け入れるコードを作成すると、 鳥 基底クラスの場合、そのコードを進化させて基底クラスで動作させることが突然難しくなります。 ジャンボジェット機 たとえば、 鳥 そして ジャンボジェット機 確かに両方とも飛べます、 ジャンボジェット機 間違いなくそうではありません 鳥.
インターフェースに入ります。
何 鳥 (そして 鷲, 、 そして 鷹) する 共通点は、すべて空を飛べることです。代わりにインターフェイス上で動作するように上記のコードを記述すると、 私は飛ぶ, 、そのコードは、そのインターフェイスに実装を提供するあらゆるものに適用できます。