誰かが私のためにインターフェイスをわかりやすく説明してくれますか、またはいくつかの良い例を教えてくれますか? あちこちでインターフェイスのポップアップが表示され続けていますが、インターフェイスの適切な説明や、それらをいつ使用するかについて実際に触れたことはありません。
インターフェイス対抽象クラスのコンテキストでインターフェイスについて話しています。
インターフェイスを使用すると、タイプではなく「説明」に対してプログラムを作成できます。これにより、ソフトウェアの要素をより緩く関連付けることができます。
このように考えてください。隣のキューブ内の誰かとデータを共有したいので、フラッシュスティックを引き出してコピー/貼り付けします。あなたは隣を歩いて、男は「それは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という新しいオブジェクトを作成するとします。そして、これはあまり良い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の観点から
インターフェイスを実装するクラスを作成するときは、インターフェイスによって定義されたすべてのメソッドとプロパティの明示的または暗黙的な実装を提供する必要があります。
さらに、.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で混乱し、Do n't Repeat Your Self(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への参照を使用している場合にのみ表示されるということです。宣言にパブリック修飾子がなく、宣言にインターフェイス名が含まれていることに注意してください。
// Explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
int IUpdatable.UpdatedByUser
{ ... }
DateTime IUpdatable.LastUpdated
{ ... }
クラスがインターフェースを実装する方法を定義するための非常に多くの柔軟性があるため、開発者はオブジェクトを消費するメソッドからオブジェクトを分離するための多くの自由を得ることができます。インターフェイスは、結合を解除するための優れた方法です。
インターフェースには、これだけではありません。これは、インターフェイスベースのプログラミングの1つの側面を利用した単純化された実際の例です。
前に述べたように、そして他のレスポンダーによって、特定のクラス参照ではなく、インターフェース参照を取得および/または返すメソッドを作成できます。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が次のようになることを保証することを宣言しています。また、コードを出荷してインターフェースを公開した後は、決して変更しないでください。コンシューマーコードがそのインターフェイスに依存し始めるとすぐに、フィールドでコードを壊したくありません。
良い議論については、このHaackedの投稿を参照してください。
抽象クラスは実装を提供できますが、インターフェイスは提供できません。NVPI(Non-Virtual Public Interface)パターンなどのガイドラインに従うと、抽象クラスはバージョン管理の面でいくつかの点でより柔軟になります。
繰り返しになりますが、.NETでは、クラスは単一のクラスからのみ継承できますが、クラスは必要な数のインターフェイスを実装できます。
インターフェイスと依存性注入(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 を作成してデータベースを読み取り、メッセージを抽出します。ファクスも間もなく実装されると突然聞くまでは、とてもうれしいことです。したがって、「ファックス」を読み取り、メッセージとして処理する必要があります。
これは簡単に Spagetti コードに変わる可能性があります。したがって、「メッセージ」のみを制御する MessageController を使用する代わりに、IMessage というインターフェイスを使用できるようにします(I は一般的な使用法ですが、必須ではありません)。
IMessage インターフェイスには、メッセージをそのまま処理できるようにするために必要ないくつかの基本データが含まれています。
したがって、EMail、Fax、PhoneCall クラスを作成するときは、それらにIMessageというインターフェイスを実装させます。
したがって、MessageController では、次のようにメソッドを呼び出すことができます。
private void ProcessMessage(IMessage oneMessage)
{
DoSomething();
}
インターフェイスを使用していない場合は、次のものが必要です。
private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.
したがって、共通のインターフェイスを使用することで、それが Fax、Email、PhoneCall などであるかどうかに関係なく、ProcessMessage メソッドがそれを操作できることを確認できました。
なぜ、またはどのように?
インターフェイスは、それを使用できるようにするために順守する (または実装する)必要があるいくつかのことを指定する契約であるためです。バッジと考えてください。オブジェクト「Fax」に IMessage インターフェイスがない場合、ProcessMessage メソッドはそれを処理できず、IMessage を必要とするメソッドに Fax を渡しているため、無効な型が返されます。物体。
ポイントがわかりますか?
インターフェイスは、実際のオブジェクト タイプに関係なく、使用できるメソッドとプロパティの「サブセット」と考えてください。元のオブジェクト (Fax、Email、PhoneCall など) がそのインターフェイスを実装している場合、そのインターフェイスを必要とするメソッド間で安全に渡すことができます。
そこにはさらに魔法が隠されています。インターフェイスを元のオブジェクトにキャストすることができます。
ファックス myFax = (ファックス)SomeIMessageThatIReceive;
.NET 1.1 の ArrayList() には、IList という優れたインターフェイスがありました。IList (非常に「汎用」) がある場合は、それを ArrayList に変換できます。
ArrayList ar = (ArrayList)SomeIList;
そして、何千ものサンプルが世の中に出回っています。
ISortable、IComparable などのインターフェイスは、その機能を実現するためにクラスに実装する必要があるメソッドとプロパティを定義します。
サンプルを拡張すると、Type が IMessage の場合、Emails、Fax、PhoneCall の List<> をすべて同じリストに含めることができますが、オブジェクトが単に Email、Fax などの場合は、それらをすべてまとめることはできません。 .
オブジェクトを並べ替える (またはたとえば列挙する) 場合は、対応するインターフェイスを実装する必要があります。.NET サンプルで、"Fax" オブジェクトのリストがあり、MyList.Sort() を使用してそれらを並べ替えたい場合は、Fax クラスを次のようにする必要があります。
public class Fax : ISorteable
{
//implement the ISorteable stuff here.
}
これがヒントになることを願っています。他のユーザーが他の良い例を投稿する可能性があります。幸運を!インターフェースの力を受け入れる。
警告: インターフェースに関してすべてが良いわけではありません。それらにはいくつかの問題があります。OOP 純粋主義者はこれについて戦争を始めるでしょう。私は脇に残ります。Interfce の欠点の 1 つ (少なくとも .NET 2.0 では) は、PRIVATE メンバーを持つことも、保護することもできず、パブリックでなければならないことです。これはある程度理にかなっていますが、単にものを非公開または保護されていると宣言したい場合があります。
関数インターフェイスはプログラミング言語内にあるだけでなく、デザインのアイデアを他の人に表現する際の強力なセマンティック ツールでもあります。
適切に設計されたインターフェイスを備えたコード ベースは、議論が非常に簡単になります。「はい、新しいリモート サーバーを登録するには CredentialsManager が必要です。」「PropertyMap を ThingFactory に渡して、作業中のインスタンスを取得します。」
複雑なことを一言で説明できる能力は非常に便利です。
インターフェイスは、「THREEPILLARSOFOOD」の1つであるポリモーフィズムの鍵でもあります。
上で触れた人もいますが、ポリモーフィズムとは、特定のクラスがさまざまな「形式」をとることができることを意味します。つまり、「Dog」と「Cat」の2つのクラスがあり、両方がインターフェイス「INeedFreshFoodAndWater」(hehe)を実装している場合、コードは次のように実行できます(擬似コード)。
INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());
foreach(INeedFreshFoodAndWater item in array)
{
item.Feed();
item.Water();
}
これは、さまざまなクラスのオブジェクトを抽象的に処理でき、オブジェクトをより緩く結合するなどの操作を実行できるため、強力です。
インターフェイスを使用すると、オブジェクトに対して一般的な方法でコーディングできます。たとえば、レポートを送信するメソッドがあるとします。ここで、新しいレポートを作成する必要がある新しい要件があるとします。すでに書いたメソッドを再利用できたらいいですね。インターフェイスを使用すると、次のことが簡単になります。
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);
}
}
わかりました、それで抽象クラスとインターフェースについてです...
概念的には、抽象クラスは基本クラスとして使用されます。多くの場合、サブクラス自体がいくつかの基本機能をすでに提供しており、サブクラスは抽象メソッド (抽象基本クラスに実装されていないメソッド) の独自の実装を提供する必要があります。
インターフェイスは主に、特定の実装の詳細からクライアント コードを分離するために使用されます。また、クライアント コードを変更せずに実装を切り替える機能により、クライアント コードがより一般的なものになる場合もあります。
技術レベルでは、一部の言語 (C++ など) では構文上の違いがないため、または分離や一般化の目的で抽象クラスを使用することもできるため、抽象クラスとインターフェイスの間に線を引くことは困難です。抽象クラスをインターフェースとして使用できるのは、すべての基本クラスが定義により、そのすべてのサブクラスが受け入れる必要があるインターフェースを定義するためです (つまり、基本クラスの代わりにサブクラスを使用できる必要があります)。
遭遇するインターフェースのほとんどは、メソッドとプロパティのシグネチャのコレクションです。インターフェイスを実装する人は誰でも、インターフェイスにあるものに定義を提供する必要があります。
インターフェイスは、継承を使用せずにオブジェクトが一定量の機能を実装することを強制する方法です (インターフェイスを使用して実現できる疎結合ではなく、強く結合されたコードになります)。
インターフェイスは、実装ではなく機能を記述します。
簡単に言えば、インターフェースはメソッドが定義したクラスですが、実装はありません。対照的に、抽象クラスにはいくつかのメソッドが実装されていますが、すべてではありません。
インターフェースを契約と考えてください。クラスがインターフェースを実装するとき、それは本質的にそのコントラクトの条件を尊重することに同意します。消費者として、あなたはあなたが持っている物が彼らの契約上の義務を遂行できることだけを気にします。それらの内部の仕組みと詳細は重要ではありません。
インターフェースは、厳密に型付けされ、多態的である方法で規則を実装する方法です。
実際の良い例は、.NET の IDisposable です。IDisposable インターフェイスを実装するクラスは、そのクラスに Dispose() メソッドを実装することを強制します。クラスが Dispose() を実装していない場合、ビルドしようとするとコンパイラ エラーが発生します。さらに、このコード パターン:
using (DisposableClass myClass = new DisposableClass())
{
// code goes here
}
実行が内側のブロックを出るときに myClass.Dispose() が自動的に実行されるようにします。
ただし、これは重要です。Dispose() メソッドが何をすべきかについて強制はありません。Dispose() メソッドでファイルからランダムなレシピを選択し、配布リストに電子メールで送信することができますが、コンパイラは気にしません。IDisposable パターンの目的は、リソースのクリーンアップを容易にすることです。クラスのインスタンスがファイル ハンドルを保持する場合、IDisposable を使用すると、割り当て解除とクリーンアップ コードを 1 か所にまとめて、常に割り当て解除が行われるようにする使用スタイルを促進することが非常に簡単になります。
そして、それがインターフェースの鍵です。これらは、プログラミング規則とデザイン パターンを効率化する方法です。これは、正しく使用すると、使いやすく、保守しやすく、より正確な、より単純で自己文書化されたコードを促進します。
Java では多重継承が許可されていません (非常に正当な理由から、恐ろしいひし形を調べてください) が、クラスにいくつかの動作セットを提供させたい場合はどうすればよいでしょうか? それを使用するすべての人に、シリアル化できることと、画面に自分自身を描画できることを知ってもらいたいとします。答えは、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 でインターフェースと抽象クラスを使用する正当な理由の 1 つは、サブクラスは複数の基本クラスを拡張できないが、複数のインターフェースを実装できることです。
インターフェイスを理解する最も簡単な方法は、クラスの継承が何を意味するかを考えることから始めることです。これには次の 2 つの側面が含まれます。
これらの機能は両方とも便利ですが、クラスが複数のクラスのメンバーを独自のものとして使用できるようにするのは難しいため、多くの言語やフレームワークでは、クラスが単一の基本クラスから継承することしか許可されていません。一方、クラスを他の複数の無関係なものに代用可能にすることには、特に問題はありません。
さらに、継承の最初の利点はカプセル化によって大部分が達成されるため、最初のタイプの多重継承を許可することによる相対的な利点はいくぶん限定されます。一方、複数の無関係なタイプのオブジェクトをオブジェクトに置き換えることができることは、言語サポートなしでは容易に達成できない有用な能力です.
インターフェイスは、言語/フレームワークが、プログラムが複数の基本型の継承の 2 番目の側面から利益を得られるようにする手段を提供します。最初の側面も提供する必要はありません。
これは、私がよく使用するデータベース関連の例です。オブジェクトと、リストのようなコンテナー オブジェクトがあるとします。オブジェクトを特定の順序で格納したい場合があるとします。シーケンスは配列内の位置に関連していませんが、代わりに、オブジェクトはより大きなオブジェクト セットのサブセットであり、シーケンス位置はデータベースの sql フィルタリングに関連していると仮定します。
カスタマイズしたシーケンス位置を追跡するには、オブジェクトにカスタム インターフェイスを実装させることができます。カスタム インターフェイスは、そのようなシーケンスを維持するために必要な組織的な作業を仲介することができます。
たとえば、関心のあるシーケンスは、レコードの主キーとは何の関係もありません。インターフェースを実装するオブジェクトでは、myObject.next() または myObject.prev() と言うことができます。
インターフェイスは、完全に抽象化されたクラスのようなものです。つまり、抽象メンバーのみを持つ抽象クラスです。複数のインターフェイスを実装することもできます。これは、複数の完全に抽象化されたクラスから継承するようなものです。とにかく..この説明は、抽象クラスとは何かを理解している場合にのみ役立ちます。
静的に型付けされたオブジェクト指向言語のインターフェイスを参照していると仮定すると、主な用途は、クラスが特定の契約またはプロトコルに従っていることを表明することです。
あなたが持っているとしましょう:
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 つのメソッドを含むクラスを実装し、抽象メソッドを使用してそのメソッドによって呼び出されるテンプレート メソッドをサポートする可能性がはるかに高くなります。次に、抽象クラスは動作の基本実装であり、すべての具象サブクラスが再実装することなく利用できます。
私はあなたと同じ問題を抱えていて、「契約」の説明が少しわかりにくいと思います。
メソッドがインパラメーターとして IEnumerable インターフェイスを受け取るように指定する場合、これは、パラメーターが IEnumerable インターフェイスから継承する型でなければならないことを指定するコントラクトであり、したがって IEnumerable インターフェイスで指定されたすべてのメソッドをサポートすると言えます。抽象クラスまたは通常のクラスを使用した場合でも、同じことが当てはまります。これらのクラスから継承するオブジェクトは、パラメーターとして渡すことができます。いずれにせよ、継承されたオブジェクトは、基本クラスが通常のクラス、抽象クラス、またはインターフェイスのいずれであっても、基本クラスのすべてのパブリック メソッドをサポートしていると言えます。
すべての抽象メソッドを持つ抽象クラスは、基本的にインターフェイスと同じであるため、インターフェイスはメソッドが実装されていない単なるクラスであると言えます。実際には言語からインターフェイスを削除し、代わりに抽象メソッドのみを持つ抽象クラスを使用することができます。それらを分離する理由はセマンティック上の理由によると思いますが、コーディング上の理由から、理由がわかりません。混乱しているだけです。
もう 1 つの提案は、インターフェイスがクラスの単なる別のバリエーションであるため、インターフェイスの名前をインターフェイス クラスに変更することです。
特定の言語では、クラスが1つのクラスのみを継承できるが複数のインターフェースを継承できるという微妙な違いがあり、他の言語では両方の多くを持つことができますが、それは別の問題であり、直接関係はないと思います
show()
単純なケースでは、実装する(またはおそらくそれを抽象として定義する)共通の基本クラスを使用して、インターフェイスで得られるものと同様のものを実現できます。一般名をMyClass1とMyClass2ではなく、 EagleとHawkというより具体的な名前に変更させてください。その場合、次のようなコードを書くことができます
Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);
これにより、 Birdであるすべてのものを処理できるコードを記述できます。その後、 Birdとして処理するインスタンスに作用すること (飛ぶ、食べる、卵を産むなど) をBirdに実行させるコードを記述できます。このコードは、 Birdが実際にEagleであるか、Hawkであるか、またはBirdから派生したものであるかに関係なく機能します。
ただし、真の関係がない場合、そのパラダイムは乱雑になり始めます。空を飛び回るコードを書きたいとします。Bird基底クラスを受け入れるようにそのコードを作成すると、そのコードをJumboJetインスタンスで動作するように進化させることが突然難しくなります。なぜなら、BirdとJumboJetは確かに両方とも飛ぶことができますが、JumboJetは間違いなくBirdではないからです。
インターフェイスに入ります。
Bird (とEagleとHawk )の共通点は、すべて空を飛べることです。インターフェイスIFlyで動作する代わりに上記のコードを記述した場合、そのコードは、そのインターフェイスに実装を提供するすべてのものに適用できます。
インターフェースでは、それらを実装するすべてのクラスに、インターフェースで定義されたメソッドが含まれている必要があります。
その目的は、クラス内のコードを見なくても、特定のタスクに使用できるかどうかを知ることができるようにすることです。たとえば、Java の Integer クラスは同等のインターフェイスを実装しているため、メソッド ヘッダー (public クラスの String が Comparable を実装している) だけを見た場合は、compareTo() メソッドが含まれていることがわかります。