# SOLID — SRP ו-OCP📖 שיעור

## מה זה?

מחלקת `Invoice` (חשבונית) שמחשבת סכום, שומרת לקובץ, **וגם** שולחת אימייל — נשמעת נוח, אבל כל שינוי בלוגיקת המייל מסכן לשבור את חישוב הסכום, כי הכל יושב יחד באותה מחלקה. SOLID הם חמישה עקרונות לתכנון מחלקות שקל לתחזק ולהרחיב — השיעור הזה מתחיל בשני הראשונים: **SRP** (Single Responsibility) אומר שלכל מחלקה צריכה להיות **סיבה אחת בלבד** להשתנות; **OCP** (Open/Closed) אומר שאפשר **להרחיב** התנהגות בלי **לשנות** קוד קיים.

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

• SRP (Single Responsibility Principle) — לכל מחלקה סיבה אחת ויחידה להשתנות; "אחריות" אחת ברורה

• OCP (Open/Closed Principle) — פתוח להרחבה (אפשר להוסיף התנהגות), סגור לשינוי (לא נוגעים בקוד קיים שכבר עובד)

• "סיבה להשתנות" — אם שינוי בדרישה עסקית אחת (למשל "שנו את פורמט המייל") מחייב לגעת בקוד שאחראי גם על משהו אחר (חישוב סכום) — זו הפרת SRP

```csharp
// Before SRP — one class with several mixed responsibilities
class Invoice
{
    public double CalculateTotal(List<double> items) => items.Sum();
    public void SaveToFile(string content) { /* write to file */ }
    public void SendEmail(string to, string content) { /* send email */ }
}
```

```csharp
// After SRP — each class is responsible for exactly one thing
class InvoiceCalculator
{
    public double CalculateTotal(List<double> items) => items.Sum();
}

class InvoiceFileWriter
{
    public void SaveToFile(string content) { /* write to file */ }
}

class InvoiceEmailSender
{
    public void SendEmail(string to, string content) { /* send email */ }
}
// A change to the email format only touches InvoiceEmailSender — the rest of the code is unaffected
```

```csharp
// OCP — adding a new discount type without touching existing code
abstract class DiscountStrategy
{
    public abstract double Apply(double price);
}

class NoDiscount : DiscountStrategy
{
    public override double Apply(double price) => price;
}

class PercentageDiscount : DiscountStrategy
{
    private double _percent;
    public PercentageDiscount(double percent) => _percent = percent;
    public override double Apply(double price) => price * (1 - _percent / 100);
}

// Adding a BlackFridayDiscount in the future = a new class, without touching existing code at all
double ApplyDiscount(double price, DiscountStrategy strategy) => strategy.Apply(price);
```

## הסבר עיקרי

SRP לא אומר "מחלקה אחת = מתודה אחת" — הוא אומר "מחלקה אחת = **אחריות** אחת". `InvoiceCalculator` יכולה להכיל כמה מתודות עזר לחישוב (מע"מ, הנחות, עיגול) — כולן עדיין תחת אחריות אחת ("חישוב"). הבעיה היא כשמחלקה אחת מכילה כמה **אחריות שונות לגמרי** (חישוב + כתיבה לקובץ + שליחת מייל) — כל אחת מהן "סיבה נפרדת להשתנות".

OCP נבנה ישירות על מה שכבר תרגלנו — `abstract class DiscountStrategy` עם `override` לכל סוג הנחה זו בדיוק ההפשטה והפולימורפיזם מהיחידה הקודמת! ההבדל הוא רק **המסגור**: OCP אומר שהמטרה של הפשטה כאן היא ספציפית — לאפשר הוספת `BlackFridayDiscount` חדש בעתיד כמחלקה **נוספת**, בלי לפתוח ולערוך את `ApplyDiscount` או את `NoDiscount`/`PercentageDiscount` הקיימות בכלל.

הקשר בין SRP ל-OCP — הם משלימים זה את זה: SRP מוודא שכל מחלקה ממוקדת מספיק כדי שלהרחיב אותה (OCP) יהיה קל ובטוח. מחלקה שכבר עמוסה בכמה אחריות (הפרת SRP) קשה הרבה יותר להרחיב בלי לשבור משהו — כי כל שינוי עלול לפגוע באחריות אחרת שיושבת באותו מקום.

## יתרונות

SRP הופך כל מחלקה לקלה יותר להבנה, בדיקה (Unit Testing) ותחזוקה בנפרד; OCP מאפשר להוסיף פיצ'רים חדשים (סוג הנחה, סוג תשלום) בלי לסכן קוד קיים שכבר נבדק ועובד בפרודקשן.

## חסרונות

פירוק יתר לפי SRP (מחלקה לכל פעולה זעירה) עלול ליצור פיזור-יתר וקשה למעקב; OCP דורש לחזות מראש איפה תרחבו בעתיד — הפשטה שגויה (במקום הלא נכון) מוסיפה מורכבות בלי תועלת אמיתית.

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

• SRP = סיבה אחת להשתנות לכל מחלקה, לא "מתודה אחת בלבד"

• OCP = פתוח להרחבה (מחלקות חדשות), סגור לשינוי (קוד קיים לא נוגעים בו)

• OCP ממומש בפועל דרך abstract class/Interface + פולימורפיזם — כלים שכבר מוכרים

• הפרת SRP מקשה על יישום OCP — מחלקה עמוסה קשה יותר להרחיב בבטחה

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

• לפרש SRP כ"מחלקה אחת = מתודה אחת בדיוק" — זו טעות; המדד הוא אחריות, לא כמות מתודות

• להוסיף התנהגות חדשה על ידי `if`/`switch` נוסף בקוד קיים במקום מחלקה חדשה — מפר OCP

• לפרק יותר מדי לפי SRP בלי צורך אמיתי, ולהפוך קוד פשוט למפוזר מדי

## סיכום

SRP אומר שלכל מחלקה צריכה להיות סיבה אחת ויחידה להשתנות — לא לערבב חישוב, שמירה ושליחת מייל תחת אותה מחלקה. OCP אומר שאפשר להרחיב התנהגות (למשל סוג הנחה חדש) על ידי הוספת מחלקה חדשה שמממשת חוזה קיים (`abstract class`/Interface), בלי לגעת בקוד קיים שכבר עובד. שני העקרונות משלימים זה את זה — SRP מייצר מחלקות ממוקדות שקל וקל להרחיב לפי OCP.

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

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

---

## תרגילים

### תרגיל 1 — פירוק לפי SRP

**המשימה:** קחו מחלקה `UserManager` היפותטית שמכילה `ValidateUser`, `SaveToDatabase` ו-`SendWelcomeEmail` יחד, ופרקו אותה לשלוש מחלקות נפרדות, כל אחת עם אחריות אחת.

**בדיקה:** כל מחלקה חדשה עוסקת באחריות אחת בלבד, וקוד קורא שמרכיב אותן יחד (למשל ב-`Main`) עדיין מבצע את אותה זרימה מקורית בהצלחה.

### תרגיל 2 — הרחבה לפי OCP

**המשימה:** הוסיפו `class FixedAmountDiscount : DiscountStrategy` (הנחה בסכום קבוע, לא אחוז) לדוגמה מהשיעור, בלי לשנות אף שורה בקוד הקיים.

**בדיקה:** `ApplyDiscount` הקיימת (שלא שונתה) עובדת נכון גם עם אסטרטגיית ההנחה החדשה.

---

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

**המשימה:** תכננו מחדש מערכת "עיבוד הזמנות" לפי SRP ו-OCP.

**דרישות:**
1. פרקו לפי SRP: `OrderCalculator` (חישוב סכום), `OrderValidator` (בדיקת תקינות), `OrderNotifier` (התראה) — כל אחת מחלקה נפרדת עם אחריות אחת
2. יישמו OCP על סוג ההתראה: `abstract class OrderNotifier` עם לפחות שני מימושים (`EmailNotifier`, `ConsoleNotifier`)
3. ב-`Main`, הרכיבו את כל המחלקות יחד לזרימת עיבוד הזמנה מלאה אחת

**בדיקה:** הוספת סוג התראה שלישי (למשל `SmsNotifier`) לא דורשת שינוי באף אחת מהמחלקות הקיימות — רק מחלקה חדשה.

## מה בפרק הבא

בפרק הבא — **SOLID: LSP, ISP ו-DIP**: שלושת העקרונות הנותרים, שמשלימים את התמונה של תכנון מחלקות גמיש ובטוח לשינוי.
