# SOLID — LSP, ISP ו-DIP📖 שיעור

## מה זה?

זוכרים את דילמת `Square : Rectangle` משיעור הירושה? זו בדיוק דוגמת-הספר להפרת **LSP** — מחלקה יורשת ש"שוברת" ציפיות מהבסיס. **ISP** אומר שממשק (Interface) לא צריך לכפות מתודות מיותרות על מי שמממש אותו. **DIP** אומר שמחלקות ברמה גבוהה צריכות להיות תלויות בהפשטות (Interfaces), לא במימושים קונקרטיים ספציפיים. שלושתם, יחד עם SRP ו-OCP מהשיעור הקודם, משלימים את חמשת עקרונות ה-SOLID.

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

• LSP (Liskov Substitution Principle) — כל מחלקה יורשת חייבת להיות ניתנת להחלפה במקום מחלקת הבסיס שלה, בלי לשבור את ההתנהגות הצפויה

• ISP (Interface Segregation Principle) — עדיף כמה Interfaces ממוקדים וקטנים על פני Interface אחד "שמן" עם מתודות שלא כולם צריכים

• DIP (Dependency Inversion Principle) — תלות בהפשטה (Interface), לא במימוש קונקרטי — מאפשר להחליף מימוש בלי לשנות את הקוד שתלוי בו

```csharp
// LSP violation — Square "breaks" the expectation set by Rectangle
class Rectangle
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }
    public double Area() => Width * Height;
}

class Square : Rectangle   // problematic — Square forces Width==Height always
{
    public override double Width
    {
        get => base.Width;
        set { base.Width = value; base.Height = value; }   // surprising! changing Width also changes Height
    }
}
// Code that expects Rectangle.Width to not affect Height breaks on Square — LSP violation
```

```csharp
// ISP — a "fat" interface forces an unneeded implementation
interface IWorker
{
    void Work();
    void Eat();   // a robot doesn't eat — still forced to implement it
}

// Better: two focused interfaces
interface IWorkable { void Work(); }
interface IEatable { void Eat(); }

class Robot : IWorkable { public void Work() { /* works */ } }        // doesn't need Eat at all
class Human : IWorkable, IEatable { public void Work() {} public void Eat() {} }
```

```csharp
// DIP — depending on an abstraction, not a concrete implementation
class OrderService
{
    private readonly INotifier _notifier;   // depends on the Interface, not a specific EmailNotifier

    public OrderService(INotifier notifier) => _notifier = notifier;   // injected from outside

    public void PlaceOrder() => _notifier.Notify("Order placed");
}

// EmailNotifier can be swapped for SmsNotifier without touching OrderService at all
var service = new OrderService(new EmailNotifier());
```

## הסבר עיקרי

LSP הוא בדיקת-תקינות ל-OCP — OCP אומר "אפשר להרחיב עם מחלקות חדשות"; LSP מוסיף תנאי: ההרחבה חייבת **להתנהג נכון** במקום המקורי. `Square : Rectangle` "מתקמפל" ועובר את כל בדיקות ה-OCP הטכניות — אבל קוד שכתוב סביב ההנחה "שינוי ב-`Width` לא משפיע על `Height`" (כמו ב-`Rectangle` הרגיל) נשבר בהפתעה כש-`Square` מוזרק במקומו. LSP אומר: אם ההחלפה משנה התנהגות צפויה, ההיררכיה שגויה.

ISP הוא SRP, אבל לממשקים — בדיוק כמו ש-SRP אומר שלמחלקה צריכה להיות אחריות אחת, ISP אומר שלממשק צריך להיות תפקיד אחד ממוקד. `IWorker` עם `Work()`+`Eat()` מכריח כל מממש "לשקר" (Robot חייב לממש `Eat()` בלי משמעות) — פיצול ל-`IWorkable`/`IEatable` פותר בדיוק את זה, כל אחד מממש רק את מה שרלוונטי לו.

DIP הוא הבסיס ל-Dependency Injection — `OrderService` לא יוצר `EmailNotifier` בעצמו (`new EmailNotifier()` בפנים) — הוא **מקבל** `INotifier` דרך הקונסטרוקטור. זה בדיוק תבנית ה-DAL (`IRepository`) שתופיע בקרוב ביחידת ASP.NET Core — המחלקה ברמה הגבוהה (`OrderService`) תלויה בחוזה (`INotifier`), לא בפרט מימוש ספציפי, כך שאפשר להחליף מימושים (לבדיקות, לשינוי עתידי) בלי לגעת ב-`OrderService` כלל.

## יתרונות

LSP מונע הפתעות התנהגותיות בהיררכיות ירושה; ISP מונע מימושים "מזויפים" של מתודות לא-רלוונטיות; DIP מאפשר להחליף מימושים (כולל למטרות בדיקה — Mock objects) בלי לגעת בקוד שתלוי בהם.

## חסרונות

בדיקת LSP דורשת חשיבה זהירה על "מה קוד קורא מצפה לו" — לא תמיד ברור מראש; ISP עם יותר מדי ממשקים זעירים עלול לפזר קוד קשור בין קבצים רבים מדי; DIP מוסיף שכבת עקיפין (Interface + הזרקה) גם למקומות שבהם מימוש יחיד לעולם לא ישתנה.

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

• LSP = מחלקה יורשת חייבת להתנהג נכון במקום הבסיס — לא רק "להתקמפל"

• ISP = ממשקים ממוקדים וקטנים עדיפים על ממשק "שמן" אחד

• DIP = תלות בהפשטה (Interface), לא במימוש קונקרטי — הבסיס ל-Dependency Injection

• חמשת העקרונות (SRP/OCP/LSP/ISP/DIP) ביחד = SOLID

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

• להניח שאם קוד מתקמפל, ההיררכיה תקינה — LSP הוא על **התנהגות**, לא רק תחביר

• לכתוב ממשק "שמן" אחד לנוחות במקום כמה ממוקדים, ולהכריח מימושים מיותרים

• ליצור תלות ישירה במימוש קונקרטי (`new EmailNotifier()` בתוך `OrderService`) במקום להזריק Interface

## סיכום

LSP מוודא שמחלקות יורשות ניתנות להחלפה בבטחה במקום מחלקת הבסיס, בלי לשבור ציפיות התנהגותיות. ISP מעדיף כמה ממשקים ממוקדים על פני ממשק "שמן" אחד שמכריח מימוש מיותר. DIP אומר שמחלקות ברמה גבוהה תלויות בהפשטות (Interfaces) ולא במימושים קונקרטיים — הבסיס לתבנית ה-Dependency Injection שתופיע ביחידת ASP.NET Core. יחד עם SRP ו-OCP, אלה חמשת עקרונות ה-SOLID.

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

[Microsoft Learn — SOLID principles](https://learn.microsoft.com/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures#the-solid-principles)

---

## תרגילים

### תרגיל 1 — זיהוי הפרת LSP

**המשימה:** קחו את דוגמת `Square : Rectangle` מהשיעור, וכתבו קוד שמדגים את ההפתעה בפועל — צרו `Rectangle rect = new Square();`, שנו רק את `Width`, והדפיסו את `Area()`.

**בדיקה:** התוצאה מפתיעה — שינוי `Width` בלבד שינה גם את `Height` בלי ציפייה לכך, מה שמוכיח את הפרת ה-LSP בפועל.

### תרגיל 2 — פיצול לפי ISP

**המשימה:** קחו `interface IMachine { void Print(); void Scan(); void Fax(); }` ופצלו אותו לשלושה ממשקים נפרדים, כך שמדפסת פשוטה (`SimplePrinter`) תממש רק `IPrintable`.

**בדיקה:** `SimplePrinter` מממשת רק את הממשק הרלוונטי לה, בלי מתודות `Scan`/`Fax` מזויפות.

---

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

**המשימה:** בנו מערכת תשלומים שמדגימה DIP עם Dependency Injection ידני.

**דרישות:**
1. `interface IPaymentGateway` עם מתודה `bool Charge(double amount)`
2. שני מימושים: `CreditCardGateway` ו-`MockPaymentGateway` (שמדמה תשלום מוצלח תמיד — שימושי לבדיקות)
3. `class CheckoutService` שמקבל `IPaymentGateway` דרך הקונסטרוקטור (לא יוצר מימוש בעצמו), ומתודה `Checkout(double amount)` שמשתמשת בו

**בדיקה:** ניתן להריץ את אותו `CheckoutService` פעם עם `CreditCardGateway` ופעם עם `MockPaymentGateway`, בלי לשנות אף שורה בתוך `CheckoutService` עצמה.

## מה בפרק הבא

בפרק הבא — **תבניות עיצוב (Design Patterns)**: פתרונות מוכרים וחוזרים לבעיות תכנון נפוצות, שרבים מהם בנויים ישירות על עקרונות SOLID שכבר תרגלנו.
