מה זה?

DiscountStrategy ו-PaymentMethod מהשיעורים הקודמים לא היו רק “דוגמאות ל-OCP” — הן דוגמאות לתבנית עיצוב מוכרת בשם Strategy. תבניות עיצוב (Design Patterns) הן פתרונות חוזרים ומוכרים לבעיות תכנון שחוזרות על עצמן בהרבה מערכות שונות — יש להן שם משותף כדי שמפתחים יוכלו לתקשר (“תשתמש כאן ב-Factory”) בלי להסביר את כל הפתרון מאפס בכל פעם. השיעור הזה נותן שם לדפוסים שכבר נתקלנו בהם, ומוסיף שניים נוספים שכיחים במיוחד.

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

• Design Pattern — פתרון מוכר וחוזר לבעיית תכנון נפוצה, עם שם מוסכם

• Strategy Pattern — כמה אלגוריתמים/התנהגויות חלופיות מאחורי Interface משותף, ניתנות להחלפה בזמן ריצה (כבר תרגלנו: DiscountStrategy)

• Singleton Pattern — מבטיח שקיים מופע יחיד בלבד ממחלקה מסוימת בכל התוכנית, עם נקודת גישה גלובלית אליו

• Factory Pattern — מרכז את לוגיקת ה-יצירה של אובייקטים במקום אחד, במקום new מפוזר בכל הקוד

// Strategy — we already practiced this! Now it has a name
// (see DiscountStrategy / PaymentMethod from previous lessons)
interface IDiscountStrategy { double Apply(double price); }
// Singleton — a single global instance
class AppConfig
{
    private static AppConfig _instance;
    private AppConfig() { }   // private constructor — new AppConfig() from outside is not allowed

    public static AppConfig Instance => _instance ??= new AppConfig();

    public string Environment { get; set; } = "Production";
}

AppConfig.Instance.Environment = "Staging";
Console.WriteLine(AppConfig.Instance.Environment);   // Staging — the same instance everywhere in the program
// Factory — centralizes complex creation logic in one place
interface INotifier { void Notify(string message); }
class EmailNotifier : INotifier { public void Notify(string m) => Console.WriteLine($"Email: {m}"); }
class SmsNotifier : INotifier { public void Notify(string m) => Console.WriteLine($"SMS: {m}"); }

static class NotifierFactory
{
    public static INotifier Create(string type) => type switch
    {
        "email" => new EmailNotifier(),
        "sms" => new SmsNotifier(),
        _ => throw new ArgumentException("Unsupported type")
    };
}

INotifier notifier = NotifierFactory.Create("email");   // calling code doesn't need to know which types exist

הסבר עיקרי

Strategy הוא בדיוק OCP במימוש — כבר בנינו אותו פעמיים (הנחות, אמצעי תשלום) בלי לדעת שיש לו שם. הרעיון תמיד זהה: הגדר Interface/abstract class משותף, ותן לקוד הקורא לבחור בזמן ריצה איזה מימוש להשתמש בו — בלי if/switch מפושט בקוד הלוגי עצמו.

Singleton פותר בעיה ספציפית — הגדרות אפליקציה גלובליות, חיבור יחיד למסד נתונים, לוגר משותף — כל אלה מקרים שבהם חייב להיות מופע יחיד, כי כמה מופעים היו יוצרים חוסר-עקביות (שתי הגדרות שונות בו-זמנית). ??= (Null-Coalescing Assignment, מוכר מה-?? שכבר למדנו) יוצר את המופע רק בפעם הראשונה שמבקשים אותו — Lazy Initialization.

Factory מפריד “מה ליצור” מ"איך משתמשים בזה" — בלי Factory, NotifierFactory.Create("email") היה מוחלף ב-new EmailNotifier() מפוזר בכל מקום שצריך Notifier — קשה להחליף לוגיקת בחירה בעתיד. עם Factory, כל לוגיקת ה"איזה מימוש ליצור" יושבת במקום אחד — קוד קורא רק מבקש “תן לי notifier מסוג email”, בלי לדעת אילו מחלקות בכלל קיימות.

יתרונות

תבניות עיצוב נותנות שפה משותפת בין מפתחים — “תשתמש ב-Factory כאן” מתקשר מיידית פתרון שלם; Strategy מאפשר החלפת התנהגות בזמן ריצה בלי if/switch מפושט; Singleton מבטיח עקביות למשאב גלובלי יחיד; Factory מרכז לוגיקת יצירה מורכבת במקום נגיש אחד.

חסרונות

Singleton בשימוש-יתר הופך לעיתים ל"מצב גלובלי מוסתר" שקשה לבדוק (Unit Test) ולעקוב אחריו; שימוש בתבנית עיצוב “כי אפשר” במקום כי היא באמת פותרת בעיה קונקרטית מוסיף מורכבות מיותרת — Over-Engineering.

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

• Strategy = אלגוריתמים חלופיים מאחורי חוזה משותף, נבחר בזמן ריצה

• Singleton = מופע יחיד גלובלי, קונסטרוקטור private, גישה דרך Property/מתודה סטטית

• Factory = לוגיקת יצירת אובייקטים מרוכזת במקום אחד, לא new מפוזר

• תבניות עיצוב הן פתרונות חוזרים לבעיות נפוצות — לא כל קוד צריך תבנית מוכרת

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

• להשתמש ב-Singleton לכל דבר “כדי שיהיה גלובלי”, גם כשזה לא באמת נדרש — מקשה על בדיקות

• לשכוח קונסטרוקטור private ב-Singleton, ולאפשר בטעות כמה מופעים עם new

• להזדקק לתבנית עיצוב “כי זה נשמע מקצועי” בלי בעיה קונקרטית שהיא פותרת

סיכום

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

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

Refactoring.Guru — Design Patterns

Microsoft Learn — Singleton pattern implementation in .NET


תרגילים

תרגיל 1 — Singleton בסיסי

המשימה: בנו class Logger עם Singleton — מתודה Log(string message) שמדפיסה עם חותמת זמן. השתמשו בו משני מקומות שונים בקוד וודאו ששניהם משתמשים באותו מופע בדיוק.

בדיקה: Logger.Instance מוחזר זהה משני מקומות שונים בקוד (אפשר לוודא עם ReferenceEquals).

תרגיל 2 — Factory

המשימה: בנו ShapeFactory שמקבלת מחרוזת (“circle”/“rectangle”) ומחזירה מופע Shape מתאים (מהיחידה הקודמת), בלי שהקוד הקורא כותב new ישירות בכלל.

בדיקה: ShapeFactory.Create("circle") מחזירה Circle תקין; קלט לא נתמך זורק שגיאה ברורה.


פרויקט מסכם

המשימה: הכינו מצגת קצרה (5 דקות) על תבנית עיצוב אחת נוספת שלא נלמדה בכיתה (למשל: Observer, Decorator, Adapter, או Builder), כולל דוגמת קוד ב-C#.

דרישות:

  1. הסבר קצר של הבעיה שהתבנית פותרת (עם דוגמה קונקרטית, לא הגדרה כללית)
  2. קטע קוד C# שממחיש את התבנית בפעולה
  3. חיבור אחד לפחות בין התבנית לעקרון SOLID רלוונטי מהיחידה (איזה עיקרון היא תומכת בו, ולמה)

בדיקה: קטע הקוד מתקמפל ורץ בפועל; ההסבר כולל דוגמה קונקרטית, לא רק הגדרת מילון.

מה בפרק הבא

בפרק הבא נעבור ליחידה החדשה — Exceptions, IO, JSON ו-LINQ: כלים מעשיים לעבודה עם קבצים, נתונים מובְנים וסינון/עיבוד אוספים, שיובילו לפרויקט המסכם הראשון של הקורס.