# Abstract Classes, sealed ופולימורפיזם📖 שיעור

## מה זה?

בשיעור הקודם `Shape` הייתה מחלקת בסיס עם `Area()` שמחזירה `0` כברירת מחדל — אבל מה זה בכלל אומר "ליצור `new Shape()` בפני עצמה"? אין "צורה" מופשטת בעולם האמיתי — רק עיגולים, מלבנים וכו'. `abstract` פותר את זה: מונע ליצור מופע ישיר של מחלקת בסיס שהיא רק "תבנית משותפת", ומחייב כל מחלקה יורשת לספק מימוש משלה לחלקים שאין להם ברירת מחדל הגיונית.

## מילות מפתח שחשוב לזכור

• `abstract class` — מחלקה שלא ניתן ליצור ממנה מופע ישירות עם `new`; קיימת רק כדי שיירשו ממנה

• `abstract` method — מתודה בלי גוף בכלל ב-Base Class; **כל** מחלקה יורשת **חייבת** לספק `override` עבורה

• `sealed` — מונע ירושה נוספת ממחלקה, או `override` נוסף על מתודה — "כאן ההיררכיה נעצרת"

• Polymorphism (פולימורפיזם) — אותה קריאת מתודה (`shape.Area()`) מתנהגת אחרת בהתאם לטיפוס האמיתי של המופע בזמן ריצה

```csharp
abstract class Shape
{
    public abstract double Area();   // no body — every subclass must implement it

    public void PrintArea()          // regular method — shared by everyone as-is
    {
        Console.WriteLine($"Area: {Area()}");
    }
}

class Circle : Shape
{
    public double Radius;
    public Circle(double radius) { Radius = radius; }
    public override double Area() => Math.PI * Radius * Radius;   // must implement
}

// Shape shape = new Shape();   // compile error — cannot instantiate an abstract class
Shape circle = new Circle(5);
circle.PrintArea();   // Area: 78.53...
```

```csharp
// Polymorphism — same call, different behavior depending on the actual type
List<Shape> shapes = new List<Shape> { new Circle(5), new Rectangle(4, 3) };
foreach (Shape shape in shapes)
{
    shape.PrintArea();   // each shape runs its own Area(), even though the variable is declared as Shape
}
```

```csharp
// sealed — stops further inheritance
sealed class FinalReport : Shape
{
    public override double Area() => 0;
}
// class ExtraReport : FinalReport { }   // compile error — FinalReport is sealed
```

## הסבר עיקרי

`abstract class` מול `abstract method` — מחלקה `abstract` היא "בלתי-ניתנת-ליצירה" בפני עצמה, אבל יכולה עדיין להכיל מתודות **רגילות עם מימוש** (כמו `PrintArea`) שמשותפות לכל היורשים כמו שהן. מתודה `abstract` בתוכה היא הצהרה בלי גוף בכלל — "כל יורש **חייב** לספק מימוש, אין ברירת מחדל שהגיונית לכולם".

למה `abstract` חזק יותר מ-`virtual` רגיל — `virtual` (מהשיעור הקודם) נותן ברירת מחדל שמחלקה יורשת **יכולה** לשכתב אם רוצה. `abstract` לא נותן שום ברירת מחדל — הקומפיילר **מכריח** כל מחלקה יורשת קונקרטית לממש אותה, אחרת שגיאת קומפילציה. זה מתאים בדיוק למקרה כמו `Area()` — אין "שטח ברירת מחדל" הגיוני לצורה כללית.

פולימורפיזם הוא התוצאה, לא מושג נפרד — כשיש `List<Shape>` עם עיגולים ומלבנים אמיתיים בפנים, קריאה ל-`shape.Area()` על כל איבר מריצה **את המימוש הנכון של הטיפוס האמיתי שלו** בזמן ריצה — לא של `Shape` (שאין לו מימוש בכלל, כי הוא abstract). זו בדיוק התוצאה המעשית של `virtual`/`override`/`abstract` יחד — הקוד הקורא (`foreach`) לא צריך לדעת בכלל איזה טיפוס קונקרטי כל איבר הוא.

## יתרונות

`abstract` מונע יצירת מופעים חסרי-משמעות של מחלקת בסיס כללית; מכריח כל מחלקה קונקרטית לספק מימוש הגיוני, ותופס שכחה בזמן קומפילציה; פולימורפיזם מאפשר קוד גנרי (`foreach` על `List<Shape>`) שעובד נכון על כל תת-טיפוס בלי `if`/`switch` על הטיפוס.

## חסרונות

`sealed` מונע הרחבה עתידית — צריך החלטה מודעת אם זה באמת רצוי; שימוש-יתר ב-`abstract` על מחלקות שבפועל אף פעם לא נגזרות מוסיף מורכבות מיותרת.

## נקודות חשובות

• `abstract class` לא ניתנת ליצירה ישירה עם `new`; קיימת רק כתבנית לירושה

• `abstract` method חייבת `override` בכל מחלקה יורשת קונקרטית — אין ברירת מחדל

• `sealed` עוצר ירושה נוספת (על מחלקה) או override נוסף (על מתודה)

• פולימורפיזם = קריאה זהה (`shape.Area()`) מריצה מימוש שונה בזמן ריצה, לפי הטיפוס האמיתי

## טעויות נפוצות

• לנסות `new Shape()` על מחלקה `abstract` ולהתפלא משגיאת קומפילציה

• לשכוח לממש מתודה `abstract` באחת ממחלקות היורשות ולקבל שגיאת קומפילציה שם

• לבלבל בין `virtual` (ברירת מחדל אופציונלית) ל-`abstract` (חובה למימוש, אין ברירת מחדל)

## סיכום

`abstract class` מונעת יצירת מופע ישיר ממחלקת בסיס שהיא רק תבנית משותפת, ו-`abstract` method מכריחה כל מחלקה יורשת קונקרטית לספק מימוש משלה. `sealed` עוצר הרחבה נוספת (ירושה או override) במפורש. פולימורפיזם הוא התוצאה המעשית של המנגנון כולו — קוד יכול לטפל בכל תתי-הטיפוסים דרך טיפוס הבסיס המשותף, וכל אחד "יודע" להריץ את ההתנהגות הנכונה לו.

## דוקומנטציה רשמית

[Microsoft Learn — Abstract and sealed classes](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/abstract)

[Microsoft Learn — Polymorphism](https://learn.microsoft.com/dotnet/csharp/fundamentals/object-oriented/polymorphism)

---

## תרגילים

### תרגיל 1 — abstract class

**המשימה:** הפכו את `Animal` (מהשיעור הקודם) ל-`abstract class` עם `abstract` method בשם `MakeSound()` בלי מימוש כלל. ודאו ש-`Dog : Animal` מספקת `override` תקין.

**בדיקה:** `new Animal()` ישירות לא מתקמפל; `new Dog()` עדיין עובד ומדפיס את הקול הנכון.

### תרגיל 2 — פולימורפיזם על List

**המשימה:** צרו `List<Animal>` עם כמה מופעי `Dog` וטיפוס יורש נוסף (`Cat` למשל), ועברו עליהם עם `foreach` תוך קריאה ל-`MakeSound()` על כל אחד.

**בדיקה:** כל איבר ברשימה מדפיס את הקול הספציפי שלו, למרות שהמשתנה בלולאה מוצהר כ-`Animal` הכללי.

---

## פרויקט מסכם

**המשימה:** הרחיבו את פרויקט `Shape` הקודם עם `sealed class Triangle : Shape` (משולש, עם `Base`/`Height`), ושדרגו את `PrintArea` להיות מוגנת מפני ירושה נוספת מ-`Triangle`.

**דרישות:**
1. `Triangle` מסומנת `sealed` — נסו (ותעדו) שקומפיילר חוסם ניסיון ליצור ממנה יורש
2. `override Area()` נכון (`base * height / 2`)
3. `List<Shape>` עם עיגול, מלבן ומשולש, ולולאה שמדפיסה את שטח כולם עם `PrintArea()`

**בדיקה:** כל שלוש הצורות מדפיסות שטח נכון דרך אותה לולאה בדיוק; ניסיון ירושה מ-`Triangle` נכשל בקומפילציה.

## מה בפרק הבא

בפרק הבא — **Interfaces**: מה עושים כשכמה מחלקות שאין ביניהן קשר "הוא-סוג-של" צריכות בכל זאת "חוזה" משותף — כמו `IComparable` שמאפשר למיין כל טיפוס.
