Interfaces — ממשקים
מה זה?
איך ממיינים List<Student> לפי גיל עם .Sort()? .NET לא יודע מה “לפני” ו"אחרי" אומר עבור Student שלכם — הוא לא מנחש. IComparable פותר את זה: “חוזה” (Interface) שאומר “כל טיפוס שמממש אותי יודע להשוות את עצמו למופע אחר מאותו סוג” — ו-.Sort() פשוט קורא לחוזה הזה, בלי לדעת שום דבר ספציפי על Student. בניגוד לירושה, אין כאן קשר “הוא-סוג-של” — Student ו-Product יכולים שניהם לממש IComparable בלי שום קשר אחד לשני.
מילות מפתח שחשוב לזכור
• Interface — “חוזה”: רשימת מתודות/Properties שכל מחלקה שמממשת אותו מתחייבת לספק, בלי מימוש משלה בתוך ה-Interface עצמו
• implements (בפועל: : IInterfaceName) — תחביר דומה לירושה, אך למחלקה מותר לממש כמה Interfaces יחד (בניגוד לירושה — רק Base Class אחת)
• IComparable<T> — Interface מובנה ב-.NET עם מתודה אחת, CompareTo, שמאפשרת מיון ו-Sort()
• Interface מול Abstract Class — Interface הוא חוזה טהור בלי מימוש כלל; Abstract Class יכולה להכיל גם מימוש משותף (ראו שיעור קודם)
class Student : IComparable<Student>
{
public string Name;
public int Age;
public Student(string name, int age)
{
Name = name;
Age = age;
}
// The IComparable<Student> "contract" requires implementing CompareTo
public int CompareTo(Student other)
{
return Age.CompareTo(other.Age); // sort by age
}
}
List<Student> students = new List<Student>
{
new Student("Dana", 25),
new Student("Avi", 20),
new Student("Rotem", 30)
};
students.Sort(); // .NET calls CompareTo behind the scenes — no need to know how Student sorts
foreach (var s in students) Console.WriteLine($"{s.Name}: {s.Age}");
// Avi: 20, Dana: 25, Rotem: 30// Several interfaces together — unlike inheritance, more than one Base Class is allowed
interface IPrintable { void Print(); }
interface ISaveable { void Save(); }
class Report : IPrintable, ISaveable
{
public void Print() => Console.WriteLine("Printing report...");
public void Save() => Console.WriteLine("Saving report...");
}הסבר עיקרי
Interface מול Abstract Class — שתיהן “לא ניתנות ליצירה ישירה”, אבל Abstract Class יכולה להכיל שדות ומתודות עם מימוש משותף (כמו PrintArea בשיעור הקודם), בעוד Interface (בגרסתו הקלאסי) הוא רק רשימת חתימות בלי שום מימוש — טהור “מה חייבים לספק”, לא “איך”. Abstract Class מתאימה לקשר “הוא-סוג-של” (Manager הוא-סוג-של Employee); Interface מתאים לקשר “יודע-לעשות” (Student יודע-להשוות-את-עצמו) בלי קשר משפחתי בין הטיפוסים.
למה IComparable שימושי כל כך — List<T>.Sort() ב-.NET לא יודע כלום על Student באופן ספציפי — היא כתובה גנרית לגמרי. היא רק דורשת ש-T ימלא את חוזה IComparable<T>. זו הסיבה שאותה Sort() בדיוק עובדת גם על List<Student>, גם על List<Product> וגם על List<int> — כולם (או שממשים את החוזה, או ש-.NET כבר יודע להשוות אותם, כמו int).
כמה Interfaces, Base Class אחת — מחלקה יכולה לרשת רק מ-Base Class אחת (C# לא תומכת בירושה מרובה), אבל לממש כמה Interfaces יחד — class Report : IPrintable, ISaveable. זו דרך גמישה לתאר “יכולות” מרובות בלי להיתקע בהיררכיית ירושה נוקשה.
יתרונות
Interface מאפשר לטיפוסים לא-קשורים לחלוק “יכולת” משותפת בלי קשר משפחתי מלאכותי; קוד ספרייה (כמו Sort()) יכול לעבוד גנרית על כל טיפוס שממלא את החוזה, בלי לדעת עליו מראש; מחלקה יכולה לממש כמה Interfaces יחד, בניגוד למגבלת ה-Base Class היחידה.
חסרונות
יותר מדי Interfaces קטנים ומפוזרים מדי עלולים להקשות על מעקב אחרי “מי בכלל מממש מה”; Interface טהור (בלי ברירות מחדל) מחייב מימוש מלא בכל מחלקה, גם אם ההתנהגות זהה בין רובן — שם Abstract Class עשויה להתאים יותר.
נקודות חשובות
• Interface = חוזה טהור, בלי מימוש; Abstract Class יכולה להכיל מימוש משותף חלקי
• מחלקה יכולה לממש כמה Interfaces, אך לרשת רק מ-Base Class אחת
• IComparable<T>.CompareTo מאפשר ל-.Sort() למיין כל טיפוס בלי לדעת עליו מראש
• Interface מתאים לקשר “יודע-לעשות”; ירושה מתאימה לקשר “הוא-סוג-של”
טעויות נפוצות
• לנסות לרשת משתי מחלקות בסיס — לא חוקי ב-C#, רק Interfaces מרובים מותרים
• לבחור ירושה כשבפועל אין קשר “הוא-סוג-של” אמיתי, רק “יכולת” משותפת — Interface מתאים יותר
• לשכוח לממש את כל חברי ה-Interface במחלקה — שגיאת קומפילציה עד שממשים הכל
סיכום
Interface הוא “חוזה” טהור — רשימת מתודות/Properties שכל מחלקה שמממשת אותו מתחייבת לספק, בלי קשר משפחתי (ירושה) בין הטיפוסים. IComparable<T> הוא דוגמה מובנית ב-.NET: מימוש CompareTo מאפשר ל-List<T>.Sort() הגנרי למיין כל טיפוס, בלי לדעת עליו שום דבר ספציפי מראש. בניגוד לירושה (Base Class יחידה), מחלקה יכולה לממש כמה Interfaces יחד.
דוקומנטציה רשמית
Microsoft Learn — IComparable<T>
תרגילים
תרגיל 1 — IComparable
המשימה: הגדירו class Product : IComparable<Product> עם Name ו-Price, שממיין לפי Price. צרו List<Product> ומיינו אותה עם .Sort().
בדיקה: אחרי .Sort(), המוצרים מסודרים מהזול ליקר.
תרגיל 2 — כמה Interfaces
המשימה: הגדירו שני Interfaces: IFlyable (מתודה Fly()) ו-ISwimmable (מתודה Swim()). צרו class Duck שמממש את שניהם.
בדיקה: duck.Fly() ו-duck.Swim() שתיהן עובדות על אותו מופע Duck.
פרויקט מסכם
המשימה: בנו מערכת ניהול משימות עם IComparable<Task> למיון לפי עדיפות.
דרישות:
class Task : IComparable<Task>עםTitle(string) ו-Priority(int, מספר גבוה יותר = דחוף יותר)CompareToשממיין כך שעדיפות גבוהה תופיע קודם (מיון יורד, לא עולה כברירת מחדל)List<Task>עם לפחות 4 משימות בעדיפויות שונות, מיון עם.Sort(), והדפסה בסדר הנכון
בדיקה: אחרי .Sort(), המשימה בעדיפות הגבוהה ביותר מופיעה ראשונה ברשימה המודפסת.
מה בפרק הבא
בפרק הבא — הכמסה, הפשטה ופולימורפיזם: סיכום שלושת העקרונות המרכזיים של OOP שכבר תרגלנו לאורך היחידה, וחיבור שלהם לתמונה אחת.