ממשק לעומת מחלקה מופשטת
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C# של Coddy. שיעור 26 מתוך 70.
גם ממשקים וגם מחלקות מופשטות מאפשרים לך להגדיר חוזים שמחלקות אחרות חייבות לפעול לפיהם, אך הם משמשים למטרות שונות ובעלי יכולות שונות.
ממשק מגדיר מה מחלקה חייבת לעשות, בלי לממש זאת. זהו חוזה טהור:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}מחלקה אבסטרקטית יכולה לספק גם חוזה וגם מימוש משותף. היא מהווה תוכנית מתאר חלקית:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // חובה לממש
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}ההבדלים העיקריים נובעים מכללי הירושה ומהמטרה.
מחלקה יכולה לממש כמה ממשקים, אך לרשת רק מחלקה מופשטת אחת.
מחלקה יכולה גם לרשת מחלקה מופשטת ולממש ממשקים בו־זמנית: אחרי הנקודתיים מופיעה תחילה מחלקת הבסיס, ואחריה הממשקים, כשהם מופרדים בפסיקים:
public class Airplane : Vehicle, IFlyable
{
public override void Drive()
{
Console.WriteLine("Taxiing to the runway");
}
public void Fly()
{
Console.WriteLine("Flying above the clouds");
}
}השתמשו בממשק כשמחלקות שאינן קשורות צריכות לחלוק התנהגות (כמו IFlyable עבור ציפורים ומטוסים). השתמשו במחלקה מופשטת כשמחלקות קשורות חולקות קוד ומצב משותפים (כמו כלי רכב שחולקים מנגנון התנעת מנוע).
למחלקות מופשטות יכולים להיות בנאים, שדות ומגבילי גישה לחברים. באופן מסורתי, לממשקים לא היו כאלה, אם כי C# מודרנית מאפשרת מימושי ברירת מחדל.
כשיש ספק, העדף ממשקים לשם גמישות — תמיד תוכל להוסיף מחלקה מופשטת בהמשך אם יהיה צורך במימוש משותף.
אתגר
קלבואו נבנה מערכת של נגן מדיה שמדגימה מתי להשתמש בממשקים לעומת מחלקות מופשטות. תיצרו תכנון שבו מכשירים שאינם קשורים חולקים התנהגות הפעלה באמצעות ממשק, בעוד שסוגי מדיה קשורים חולקים מימוש משותף באמצעות מחלקה מופשטת.
תארגנו את הקוד שלכם בארבעה קבצים:
IPlayable.cs: הגדירו ממשק בשםIPlayableבמרחב השמותMedia. ממשק זה מייצג את החוזה עבור כל דבר שיכול להפעיל מדיה — בין אם זה טלפון, טלוויזיה או נגן מוזיקה ייעודי. עליו להצהיר על מתודה יחידהPlay()שמחזירה מחרוזת.MediaFile.cs: הגדירו מחלקה מופשטת בשםMediaFileבמרחב השמותMedia. היא מייצגת את התשתית המשותפת לכל קובצי המדיה. עליה לכלול מאפייןTitle(מחרוזת) ומאפייןDuration(מספר שלם, המייצג שניות). כללו בנאי שמגדיר את שני הערכים. הוסיפו מתודה קונקרטית בשםGetInfo()שמחזירה"{Title} ({Duration}s)". הצהירו גם על מתודה מופשטתGetType()שמחזירה מחרוזת — כל סוג מדיה יגדיר איזה סוג קובץ הוא.AudioFile.cs: הגדירו מחלקה בשםAudioFileבמרחב השמותMedia, שיורשת מ־MediaFileומממשת אתIPlayable. הוסיפו מאפייןArtist(מחרוזת). הבנאי אמור לקבל כותרת, משך זמן ואמן. דרסו אתGetType()כך שתחזיר"Audio". ממשו אתPlay()כך שתחזיר"Playing audio: {Title} by {Artist}".Program.cs: בקובץ הראשי שלכם, צרוAudioFileבאמצעות ערכי הקלט. הדגימו את שני ההיבטים של המחלקה: קראו ל־GetInfo()ול־GetType()(שירשנו מהמחלקה המופשטת), ואז קראו ל־Play()(מהממשק). אחסנו את קובץ השמע במשתנה מסוגIPlayableוקראו שוב ל־Play()כדי להדגים פולימורפיזם באמצעות ממשק.
תקבלו שלושה ערכי קלט:
- שם השיר
- משך הזמן בשניות
- שם האמן
הדפיסו את הפלט בפורמט הבא:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}לדוגמה, אם ערכי הקלט הם Bohemian Rhapsody, 354 ו־Queen, הפלט אמור להיות:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by Queenשימו לב איך AudioFile נהנית משתי הגישות: היא יורשת מימוש משותף (GetInfo()) מהמחלקה המופשטת, ובמקביל מקיימת את החוזה של IPlayable. המחלקה המופשטת מאפשרת שימוש חוזר בקוד עבור סוגי מדיה קשורים, ואילו הממשק מאפשר לכל מחלקה שאינה קשורה להיות "ניתנת להפעלה" בלי לשתף היררכיית ירושה.
נסו בעצמכם
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// קרא את ערכי הקלט
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: צור AudioFile באמצעות ערכי הקלט
// TODO: הדפס Info: ואחריו את התוצאה של GetInfo()
// TODO: הדפס Type: ואחריו את התוצאה של GetType()
// TODO: הדפס Direct: ואחריו את התוצאה של Play()
// TODO: אחסן את קובץ השמע במשתנה מסוג IPlayable
// TODO: הדפס Via Interface: ואחריו את התוצאה של Play() מהמשתנה מסוג IPlayable
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמרחבי שמות והנחיותמבוא למחלקות ולאובייקטיםמילת המפתח 'this'מתודות ופרמטריםשדות לעומת מאפייניםבנאיםאתחול אובייקטיםסיכום – מחשבון פשוט4ירושה
תחביר ירושה בסיסית (:)מילת המפתח 'base'מילות המפתח Virtual ו-Overrideמחלקות Sealedמחלקת הבסיס 'object'סיכום – היררכיית עובדים7תכונות מתקדמות
העמסת אופרטוריםאינדקסרים (this[])דריסת ToString()מתודות הרחבהסיכום – רשימה מותאמת אישית2מאפיינים וחברים סטטיים
מאפיינים במימוש אוטומטימאפיינים לקריאה בלבד ולכתיבה בלבדשדות ומתודות סטטייםמחלקות סטטיותחברים עם גוף ביטוי5פולימורפיזם וממשקים
פולימורפיזם בזמן קומפילציה לעומת בזמן ריצהממשק לעומת מחלקה מופשטתממשקים מרוביםממשקים מפורשיםהמרה כלפי מעלה והמרה כלפי מטהחזרה: מחשבון צורות8מושגים מתקדמים בתכנות מונחה עצמים
קומפוזיציה במקום ירושהGenerics (מחלקות ומתודות)נציגים ואירועיםמאפיינים ורפלקציהIDisposable והצהרת usingיסודות הזרקת תלויותתרגלו בעצמכם: קומפיילר C# אונליין