תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

דפוס adapter ב-PHP

מחבר:
בתאריך:

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

באסה! או אולי לא, אם משתמשים בדפוס האדפטר ( adapter design pattern ).

adapter, כפי שמעיד שמו, הוא מתאם. ובמקרה שלנו, מדובר בקוד שמתאם בין ספריות קוד שעושות את אותם משימות, אבל משתמשות בשמות שונים למתודות.

המחלקה להלן ששמה PayWithPayZilla משמשת לסליקה באמצעות ספק הסליקה PayZilla. המחלקה מוסיפה פריט לסליקה באמצעות המתודה addItem, ואת מחירו של הפריט באמצעות המתודה addPrice.

class PayWithPayZilla {
 
  function addItem($itemName)
  {
    var_dump("1 item added: ".$itemName );
  }
 	
  function addPrice($itemPrice)
  {
    var_dump("1 item added to total with the price of: ".$itemPrice );
  }
}

כדי לבצע תשלום, נוסיף מחלקה ששמה Customer שמשתמשת במחלקה PayWithPayZilla על מנת לשלם:

// use case
class Customer{
  private $pay;
 	
  function __construct($pay)
  {
    $this->pay = $pay;
  }
 	
  function buy($itemName, $itemPrice)
  {
    $this->pay -> addItem($itemName);
    $this->pay -> addPrice($itemPrice);
  }
}

ועכשיו, נפעיל את המחלקה Customer כדי לשלם על סוכרייה על מקל שמחירה 2 שקלים.

$pay = new PayWithPayZilla();
$customer = new Customer($pay);
$customer -> buy('lollipop',2);

התוצאה:

string '1 item added: lollipop' (length=22)
string '1 item added to total with the price of: 2' (length=42)

 

הבעיה

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

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

זה קוד הספרייה PayKal:

class PayKal {
  function addOneItem($name)
  {
    var_dump("1 item added: ".$name);
  }
 	
  function addPriceToTotal($price)
  {
    var_dump("1 item added to total with the price of: ".$price);
  }
 	
  // Unique method
  function addItemAndPrice($name,$price)
  {
    $this->addOneItem($name);
    $this->addPriceToTotal($price);
  }
}

למרות ש-addOneItem מוסיפה את שם הפריט לסליקה במחלקה PayKal, בדיוק כפי ש-addItem עושה בשביל במחלקה PayZilla. ולמרות ש-addPriceToTotal מוסיף את מחיר הפריט לסך הכול, בדיוק כפי שעושה המתודה addPrice במחלקה המקורית. אם ננסה להעביר למחלקה Customer אובייקט של PayKal, במקום של PayZilla , נגרום לשגיאה פטלית.

בואו ננסה את זה:

$pay = new PayKal();
$customer = new Customer($pay);
$customer -> buy('lollipop',2);

והתוצאה היא, כצפוי, שגיאה פטלית:

Fatal error: Call to undefined method PayKal::addItem()

למרות שהספריות מבצעות את אותם הפעולות, אנחנו לא יכולים להחליף בין PayKal ל-PayZilla בגלל שהם משתמשות בשמות שונים למתודות.

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

ובמקרה שלנו, עלינו להתאים את הקוד של המחלקה החדשה, המחלקה payKal, לקוד המחלקה הקיימת, payZilla.

כדי לבצע את ההתאמה, דפוס האדפטר ממליץ ליצור מחלקת אדפטר שיש לה שתי תכונות:

  1. מיישמת את הממשק של המחלקה המקורית.
  2. מחזיקה רפרנס למחלקה החדשה.

ניגש למלאכת כתיבת מחלקת האדפטר:

 

1. ניצור אינטרפייס של המחלקה החדשה

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

ניצור את האינטרפייס הבא, שכולל את שתי המתודות של המחלקה PayWithPayZilla. נקרא לו, בהתאם, PayZilla:

interface PayZilla {
  function addItem($itemName);
 	
  function addPrice($itemPrice);
}

ועכשיו המחלקה המקורית, PayWithPayZilla, צריכה ליישם את האינטרפייס שזה עתה מיצינו בשבילה:

class PayWithPayZilla implements PayZilla {
 
  function addItem($itemName)
  {
    var_dump( "1 item added: ".$itemName );
  }
 
  function addPrice($itemPrice)
  {
    var_dump( "1 item added to total with the price of: ".$itemPrice );
  }
}

 

2. נכתוב מחלקת אדפטר שמיישמת את האינטרפייס

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

כך נראית מחלקת האדפטר שמתאמת את המחלקה החדשה, PayKal, למחלקה המקורית, PayZilla, לה נקרא בהתאם לתפקידה: PayKal2PayZillaAdapter:

 class PayKal2PayZillaAdapter implements PayZilla {}

מלבד יישום האינטרפייס, האדפטר צריך להחזיק רפרנס למחלקה החדשה. לשם כך, נזריק את המחלקה החדשה דרך הקונסטרקטור, ונחזיק את הרפרנס במשתנה הפרטי payObj$.

// The adapter implements the original class
class PayKal2PayZillaAdapter implements PayZilla {
           
  // The adapter holds a reference to the new class.
  private $payObj;
   
  // In order to hold a reference, we need to pass the new 
  //   class's object throught the constructor.
  function __construct($payObj)
  {
    $this->payObj = $payObj; 
  }
}

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

// The adapter implements the original class
class PayKal2PayZillaAdapter implements PayZilla {
               
  // The adapter holds a reference to the new class.
  private $payObj;
 	  
   // In order to hold a reference, we need to pass the new 
   //   class's object throught the constructor.
   function __construct($payObj)
  {
    $this->payObj = $payObj; 
  }
 	  
  // The name of the methods is that of the old class.
  // The code within the methods uses the code of the new class.
  function addItem($itemName)
  {
    $this->payObj->addOneItem($itemName);
  }
}

כך נראית המחלקה המלאה:

// The adapter implements the original class
class PayKal2PayZillaAdapter implements PayZilla {
               
  // The adapter holds a reference to the new class.
  private $payObj;
 	  
  // In order to hold a reference, we need to pass the new 
  //   class's object throught the constructor.
  function __construct($payObj)
  {
    $this->payObj = $payObj; 
  }
 	  
  // The name of the methods is that of the old class.
  // The code within the methods uses the code of the new class.
  function addItem($itemName)
  {
    $this->payObj->addOneItem($itemName);
  }
 	  
  function addPrice($itemPrice)
  {
    $this->payObj->addPriceToTotal($itemPrice);
  }
 	  
  // Exists only in the adopted.
  function addItemAndPrice($name,$price)
  {
    $this->payObj->addOneItem($name);
    $this->payObj->addPriceToTotal($price);
  }	
}
 
 
$payKal = new PayKal();
$pay = new PayKal2PayZillaAdapter($payKal);
$customer = new Customer($pay);
$customer -> buy('lollipop',2);

והתוצאה היא אותה תוצאה.

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

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

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

 

לסיכום

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

 

אני ממליץ ללמוד PHP מונחה עצמים עם "The essentials of Object Oriented PHP" שהוא הספר שעליו מבוססים רוב המדריכים בנושא באתר רשתטק.
הקליקו על התמונה כדי לרכוש את ה-eBook:

eBook cover The essentials of Object Oriented PHP

 

לכל מדריכי ה-PHP מונחה עצמים

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

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

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

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

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

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

דג למים הוא כמו ציפור ל...?