תבנית העיצוב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 72 מתוך 87.
תבנית הדקורטור היא תבנית עיצוב מבנית שמאפשרת לך להוסיף התנהגויות חדשות לאובייקטים באמצעות עטיפתם באובייקטים מיוחדים שנקראים דקורטורים. בניגוד לירושה, שמוסיפה התנהגות בזמן הקומפילציה, דקורטורים מוסיפים פונקציונליות באופן דינמי בזמן הריצה, מבלי לשנות את המחלקה המקורית.
דמיינו בית קפה שבו מתחילים עם קפה בסיסי ואפשר להוסיף תוספות כמו חלב, סוכר או קצפת. כל תוספת עוטפת את הקפה המקורי ומוסיפה לו את העלות ואת התיאור שלה. התבנית משתמשת בממשק משותף, כך שאפשר להתייחס לאובייקטים המעוטרים באותו אופן כמו לאובייקט המקורי:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}הדקורטור מחזיק הפניה לאובייקט העטוף ומעביר אליו קריאות, תוך הוספת התנהגות משלו. אפשר לערום כמה דקורטורים כדי לשלב תכונות:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // קפה פשוט, חלב, סוכר
System.out.println(order.getCost()); // 2.75תבנית הדקורטור מצטיינת כשצריך להוסיף אחריות לאובייקטים בלי ליצור התפוצצות של תתי־מחלקות. כל דקורטור הוא עצמאי, כך שאפשר לשלב ביניהם בחופשיות וליצור בדיוק את שילוב ההתנהגויות הדרוש לך.
אתגר
קלבואו נבנה מערכת התראות באמצעות תבנית העיצוב Decorator! תיצור התראת בסיס שאפשר להרחיב באמצעות ערוצי מסירה שונים — התראות בדוא״ל, התראות SMS והתראות Push — על ידי עטיפת מעטרים זה בזה. כך פועלות מערכות בעולם האמיתי, שבהן ייתכן שצריך לשלוח הודעה אחת בכמה ערוצים בו־זמנית.
תארגן את הקוד שלך בארבעה קבצים:
Notifier.java: הגדר את הממשקNotifier, המשמש כממשק הרכיב של תבנית העיצוב שלך. עליו להצהיר על השיטהsend(String message), שמטפלת בשליחת התראות.BasicNotifier.java: צור את מחלקת הרכיב הקונקרטית שמממשת אתNotifier. היא מייצגת את צורת ההתראה הפשוטה ביותר: התראה בתוך האפליקציה. כשקוראים ל-send, עליה להדפיסIn-App: [message].NotifierDecorators.java: בנה כאן את היררכיית המעטרים שלך. התחל במחלקה מופשטתNotifierDecoratorשמממשת אתNotifierומחזיקה הפניה לאובייקטNotifierעטוף (שמועבר דרך הבנאי שלה).לאחר מכן, צור שלושה מעטרים קונקרטיים שיורשים מ-
NotifierDecorator:EmailDecorator- כשקוראים ל-send, הוא תחילה מעביר את הקריאה למודיע העטוף, ואז מדפיסEmail: [message].SMSDecorator- כשקוראים ל-send, הוא תחילה מעביר את הקריאה למודיע העטוף, ואז מדפיסSMS: [message].PushDecorator- כשקוראים ל-send, הוא תחילה מעביר את הקריאה למודיע העטוף, ואז מדפיסPush: [message].Main.java: חבר את מערכת המעטרים שלך! יתקבלו שתי קלטים: הודעת ההתראה (String) ורשימה מופרדת בפסיקים של הערוצים להוספה (String), לדוגמה"email,sms"או"push,email,sms".התחל עם
BasicNotifier. לאחר מכן, בהתאם לקלט הערוצים, עטוף אותו במעטרים המתאימים לפי הסדר שבו הם מופיעים. שמות הערוצים התקפים הםemail,smsו-push.לבסוף, קרא ל-
sendעם ההודעה שלך במודיע שעוטר במלואו.
יתקבלו שתי קלטים לפי הסדר: ההודעה לשליחה (String) והערוצים להפעלה (String, מופרדים בפסיקים).
לדוגמה, עבור הקלטים Server is down! ו-email,push, הפלט שלך יהיה:
In-App: Server is down!
Email: Server is down!
Push: Server is down!שים לב שכל מעטר מוסיף התנהגות משלו תוך שמירה על ההתראה המקורית. סדר העטיפה קובע את סדר הפלט: הרכיב הפנימי ביותר (BasicNotifier) מתבצע ראשון, ואז כל מעטר מוסיף את השכבה שלו. אפשר לשלב כל צירוף של ערוצים בלי ליצור מחלקות נפרדות לכל צירוף!
נסו בעצמכם
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// קראו את ההודעה שיש לשלוח
String message = scanner.nextLine();
// קראו את רשימת הערוצים המופרדים בפסיקים (למשל, "email,sms" או "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: התחילו עם BasicNotifier
// TODO: פצלו את קלט הערוצים לפי פסיקים
// TODO: עברו על כל ערוץ ועטפו את רכיב ההתראות בדקורטור המתאים
// הערוצים התקפים הם: "email", "sms", "push"
// TODO: קראו ל-send של רכיב ההתראות לאחר שעטפתם אותו בכל הדקורטורים, והעבירו לו את ההודעה
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומים9גנריות
מבוא לגנריותמחלקות גנריותשיטות גנריותפרמטרי טיפוס מוגבליםתווים כלליים (?, extends, super)סיכום – מכל גנרי12תבניות עיצוב — חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית המתודה התבניתיתתבנית המצבתבנית המרוכבתתבנית האיטרטורתרגלו בעצמכם: קומפיילר Java אונליין