פולימורפיזם באמצעות ממשקים
חלק מהיחידה תכנות מונחה עצמים במסלול ה-PHP של Coddy. שיעור 34 מתוך 91.
בשיעור הקודם ראית פולימורפיזם באמצעות ירושה, שבו מחלקות-ילד דורסות מתודות של מחלקות-הורה. ממשקים מספקים דרך חזקה נוספת להשיג פולימורפיזם, במיוחד כשעובדים עם מחלקות שאינן קשורות זו לזו אך חולקות התנהגות משותפת.
בממשקים, פולימורפיזם נובע מהחוזה עצמו. כל מחלקה שמממשת ממשק מבטיחה שיש לה שיטות מסוימות, ללא קשר להיררכיית הירושה שלה:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
פלט:
Email: Hello
SMS: Hello
Push: Helloהפונקציה notify() עובדת עם כל אובייקט Notifiable. למחלקות האלה אין מחלקת אב משותפת — הן אינן קשורות זו לזו כלל, מלבד העובדה שהן מממשות את אותו ממשק. זהו היתרון המרכזי על פני פולימורפיזם המבוסס על ירושה: אפשר לקבץ מחלקות שאינן קשורות זו לזו לפי היכולות שלהן במקום לפי השושלת שלהן.
נקודה מרכזית: פולימורפיזם מבוסס ממשקים מאפשר להשתמש במחלקות שאינן קשורות זו לזו באופן חליפי, על סמך התנהגות משותפת, וכך הופך את הקוד שלך לגמיש יותר ולתלוי פחות במימושים ספציפיים.
אתגר
קלבואו נבנה מערכת אחסון המדגימה פולימורפיזם המבוסס על ממשקים. תיצרו מנגנוני אחסון שונים — מערכת קבצים ובסיס נתונים — שאין להם שום דבר במשותף, פרט לכך ששניהם מממשים את אותו ממשק אחסון. פונקציה יחידה תפעל עם כל סוג של אחסון, וכך תדגים כיצד ממשקים מאפשרים פולימורפיזם בין מחלקות שאינן קשורות זו לזו כלל.
תארגנו את הקוד שלכם בארבעה קבצים:
Storable.php: הגדירו ממשקStorableעם חתימות של שתי מתודות:save($key, $data)ו-retrieve($key). חוזה זה מבטיח שכל מערכת אחסון תוכל לאחסן ולאחזר נתונים, בלי קשר לאופן שבו היא ממומשת פנימית.FileStorage.php: צרו מחלקהFileStorageשמממשת אתStorable. כללו את קובץ הממשק. למחלקה צריכה להיות תכונה פרטית$directoryשמוגדרת באמצעות הבנאי. ממשו אתsave($key, $data)כך שתחזיר"Saving '[data]' to file [directory]/[key].txt". ממשו אתretrieve($key)כך שתחזיר"Reading from file [directory]/[key].txt".DatabaseStorage.php: צרו מחלקהDatabaseStorageשגם היא מממשת אתStorable. כללו את קובץ הממשק. למחלקה צריכה להיות תכונה פרטית$tableNameשמוגדרת באמצעות הבנאי. ממשו אתsave($key, $data)כך שתחזיר"Inserting '[data]' into table [tableName] with key [key]". ממשו אתretrieve($key)כך שתחזיר"Selecting from table [tableName] where key = [key]".main.php: כללו את שני קובצי האחסון. צרו פונקציה בשםstoreDataשמקבלת פרמטר מסוגStorable, מפתח ונתונים. הפונקציה צריכה להחזיר את התוצאה של קריאה ל-save()עם המפתח והנתונים. צרו פונקציה נוספת בשםfetchDataשמקבלת פרמטר מסוגStorableומפתח, ומחזירה את התוצאה של קריאה ל-retrieve().
תקבלו ארבעה קלטים: נתיב של תיקייה, שם של טבלה, מפתח ונתונים לאחסון. צרו FileStorage עם התיקייה ו-DatabaseStorage עם שם הטבלה. השתמשו בפונקציה storeData() כדי לשמור את הנתונים תחילה באמצעות אחסון הקבצים, ולאחר מכן באמצעות אחסון בסיס הנתונים. הדפיסו כל תוצאה בשורה נפרדת. לאחר מכן השתמשו ב-fetchData() כדי לאחזר נתונים מאחסון הקבצים ולהדפיס את התוצאה.
שימו לב ש-storeData() ו-fetchData() פועלות באופן זהה עם שני סוגי האחסון. הן מתייחסות רק לכך שהאובייקט מממש את Storable. למערכת הקבצים ולבסיס הנתונים יש מימושים פנימיים שונים לחלוטין, ובכל זאת אפשר להחליף ביניהם באמצעות חוזה הממשק.
נסו בעצמכם
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// קראו את הקלטים
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: צרו פונקציה storeData שמקבלת Storable, מפתח ונתונים
// עליה להחזיר את התוצאה של קריאה ל-save() עם המפתח והנתונים
// TODO: צרו פונקציה fetchData שמקבלת Storable ומפתח
// עליה להחזיר את התוצאה של קריאה ל-retrieve() עם המפתח
// TODO: צרו מופע של FileStorage עם התיקייה
// TODO: צרו מופע של DatabaseStorage עם שם הטבלה
// TODO: השתמשו ב-storeData() כדי לשמור נתונים באחסון קבצים, ואז הדפיסו את התוצאה
// TODO: השתמשו ב-storeData() כדי לשמור נתונים באחסון מסד נתונים, ואז הדפיסו את התוצאה
// TODO: השתמשו ב-fetchData() כדי לאחזר נתונים מאחסון קבצים, ואז הדפיסו את התוצאה
?>השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים (OOP)
קבצים חיצונייםמבוא ל-OOPמחלקות לעומת אובייקטיםמילת המפתח $thisמתודותמאפייניםבנאי (__construct)הורס (__destruct)חזרה: מחשבון פשוט4ירושה
ירושה בסיסיתמילת המפתח parent::דריסת מתודותמילת המפתח finalמחלקות מופשטותסיכום – היררכיית עובדים7כימוס
ציבורי, מוגן, פרטימפרטי גישה לעומקמתודות גישה ועדכוןהסתרת מידעקידום פרמטרים בבנאי (8.0)סיכום – מערכת רישומי תלמידים2מרחבי שמות וטעינה אוטומטית
מבוא למרחבי שמותמילת המפתח useתקן הטעינה האוטומטית PSR-4הטוען האוטומטי של Composerסיכום – פרויקט מאורגן5ממשקים וחוזים
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםממשק לעומת מחלקה מופשטתרמזי טיפוסים עם ממשקיםסיכום - מחשבון צורות8מתודות קסם
מבוא למתודות קסם__toString ו־__debugInfo__get, __set, __isset, __unset__call ו־__callStatic__clone ושיבוט אובייקטים__serialize ו־__unserializeסיכום – אוסף מותאם אישית3מאפייני מחלקה
מאפייני מופע לעומת מאפיינים סטטייםקבועים במחלקותמתודות ומאפיינים סטטייםמאפיינים פרטיים ומוגניםמאפיינים לקריאה בלבד (PHP 8.1)סיכום – מנהל חשבון בנק6פולימורפיזם
בחינה מחדש של דריסת מתודותפולימורפיזם באמצעות ממשקיםרמזי טיפוסים וטיפוסי איחודקישור סטטי מאוחרסיכום — מעבד תשלומיםתרגלו בעצמכם: קומפיילר PHP אונליין