Menu
Coddy logo textTech

תבנית העיצוב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C# של Coddy. שיעור 58 מתוך 70.

תבנית Decorator היא תבנית מבנית שמאפשרת לך להוסיף התנהגויות חדשות לאובייקטים באמצעות עטיפתם באובייקטים מיוחדים שנקראים מעטרים. בניגוד לירושה, שמוסיפה התנהגות בזמן הקומפילציה, מעטרים מוסיפים פונקציונליות באופן דינמי בזמן הריצה, בלי לשנות את המחלקה המקורית.

התבנית פועלת בכך שהמעטרים מממשים את אותו ממשק כמו האובייקט שהם עוטפים. כל מעטר מחזיק הפניה לאובייקט העטוף ומעביר אליו קריאות, תוך הוספת ההתנהגות שלו לפני כן או לאחר מכן:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

העוצמה של דקורטורים טמונה ביכולת שלהם להיערם. אפשר לעטוף אובייקט שעבר דקורציה בדקורטור נוסף:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // חלב כפול!

Console.WriteLine(coffee.GetDescription());  // קפה, חלב, חלב
Console.WriteLine(coffee.GetCost());         // 3.00

תבנית העיצוב Decorator אידיאלית כשצריך להוסיף אחריויות לאובייקטים בלי ליצור התפוצצות של מחלקות משנה. כל מקשט מתמקד בהיבט יחיד, ומשלבים אותם לפי הצורך כדי לבנות בדיוק את ההתנהגות הרצויה.

challenge icon

אתגר

קל

בואו נבנה מערכת להזמנת פיצה באמצעות דפוס Decorator. תיצרו פיצה בסיסית ואז תוסיפו לה תוספות באופן דינמי, כאשר כל תוספת עוטפת את השכבה הקודמת ומוסיפה גם לתיאור וגם למחיר.

תארגנו את הקוד בשלושה קבצים:

  • Pizza.cs: הגדירו ממשק IPizza במרחב השמות PizzaShop עם שתי מתודות: GetDescription() שמחזירה מחרוזת ו-GetPrice() שמחזירה מספר עשרוני. לאחר מכן צרו מחלקה PlainPizza שמממשת את הממשק הזה — היא צריכה להחזיר "Pizza" כתיאור שלה ואת 8.00m כמחיר הבסיס שלה.
  • ToppingDecorators.cs: צרו מחלקה אבסטרקטית ToppingDecorator שמממשת את IPizza ומחזיקה הפניה לפיצה העטופה (שמועברת דרך הבנאי). המתודות הווירטואליות שלה צריכות להעביר את הקריאות לפיצה העטופה. לאחר מכן צרו שלוש מחלקות עוטפות קונקרטיות:
    • CheeseTopping — מוסיפה ", Cheese" לתיאור ומוסיפה 1.50m למחיר
    • PepperoniTopping — מוסיפה ", Pepperoni" לתיאור ומוסיפה 2.00m למחיר
    • MushroomTopping — מוסיפה ", Mushrooms" לתיאור ומוסיפה 1.25m למחיר
  • Program.cs: חברו את הכול יחד על ידי התחלה עם פיצה פשוטה ועטיפתה בתוספות בהתאם לקלט. כל מחלקה עוטפת של תוספת עוטפת את הפיצה הקודמת, וכך ההזמנה הסופית נבנית שכבה אחר שכבה.

תקבלו קלט אחד:

  • רשימה מופרדת בפסיקים של תוספות להוספה (לדוגמה, cheese,pepperoni,cheese)

התחילו עם PlainPizza, ואז עטפו אותה במחלקה העוטפת המתאימה עבור כל תוספת, לפי הסדר שבו התוספות מופיעות. התוספות התקפות הן cheese, pepperoni ו-mushrooms. לאחר הוספת כל התוספות, הדפיסו את התיאור בשורה אחת ואת המחיר הכולל בשורה הבאה.

לדוגמה, אם הקלט הוא cheese,pepperoni,mushrooms, הפלט צריך להיות:

Pizza, Cheese, Pepperoni, Mushrooms
12.75

שימו לב שאפשר להוסיף את אותה התוספת כמה פעמים! אם הקלט הוא cheese,cheese, תקבלו:

Pizza, Cheese, Cheese
11.00

הדבר מדגים את הגמישות של דפוס ה-Decorator — כל מחלקה עוטפת את הפיצה שהיא מקבלת, בין אם זו פיצה פשוטה ובין אם זו פיצה שכבר נוספו לה תוספות, וכך אפשר לערום התנהגויות באופן דינמי!

נסו בעצמכם

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // קראו את רשימת התוספות המופרדות בפסיקים
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: התחילו עם PlainPizza
        
        // TODO: עברו על כל תוספת ועטפו את הפיצה
        // בדקורטור המתאים לפי שם התוספת:
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: הדפיסו את התיאור והמחיר הסופיים
    }
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C# אונליין