תבנית העיצוב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 אידיאלית כשצריך להוסיף אחריויות לאובייקטים בלי ליצור התפוצצות של מחלקות משנה. כל מקשט מתמקד בהיבט יחיד, ומשלבים אותם לפי הצורך כדי לבנות בדיוק את ההתנהגות הרצויה.
אתגר
קלבואו נבנה מערכת להזמנת פיצה באמצעות דפוס 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: הדפיסו את התיאור והמחיר הסופיים
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמרחבי שמות והנחיותמבוא למחלקות ולאובייקטיםמילת המפתח 'this'מתודות ופרמטריםשדות לעומת מאפייניםבנאיםאתחול אובייקטיםסיכום – מחשבון פשוט4ירושה
תחביר ירושה בסיסית (:)מילת המפתח 'base'מילות המפתח Virtual ו-Overrideמחלקות Sealedמחלקת הבסיס 'object'סיכום – היררכיית עובדים7תכונות מתקדמות
העמסת אופרטוריםאינדקסרים (this[])דריסת ToString()מתודות הרחבהסיכום – רשימה מותאמת אישית2מאפיינים וחברים סטטיים
מאפיינים במימוש אוטומטימאפיינים לקריאה בלבד ולכתיבה בלבדשדות ומתודות סטטייםמחלקות סטטיותחברים עם גוף ביטוי5פולימורפיזם וממשקים
פולימורפיזם בזמן קומפילציה לעומת בזמן ריצהממשק לעומת מחלקה מופשטתממשקים מרוביםממשקים מפורשיםהמרה כלפי מעלה והמרה כלפי מטהחזרה: מחשבון צורות8מושגים מתקדמים בתכנות מונחה עצמים
קומפוזיציה במקום ירושהGenerics (מחלקות ומתודות)נציגים ואירועיםמאפיינים ורפלקציהIDisposable והצהרת usingיסודות הזרקת תלויותתרגלו בעצמכם: קומפיילר C# אונליין