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

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

מדריך factory design pattern ב-PHP

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

במדריך הקודם הסברנו את הנושא של MVC שהוא כנראה הדפוס התכנותי החשוב ביותר למתכנת ה-PHP. במדריך זה נכיר דפוס תכנותי שימושי נוסף ששמו Factory (או בעברית, מפעל). בדפוס ה-Factory משתמשים במקרים שכדאי לנו שהחלק העיקרי של התוכנה (Business logic) יעסוק רק בניהול אובייקטים, ולא בייצור שלהם. במקרים אילה, דפוס ה-factory מציע שייצור האובייקטים ייעשה בקלאס Factory נפרד מהחלק העיקרי של התוכנה.

דוגמאות

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

מה מאפיין את דפוס ה-factory?

שני המאפיינים העיקריים של דפוס ה-factory הם:

  1. החלק העיקרי של התוכנה שמנהל אובייקטים, אבל לא מייצר אותם.
  2. מחלקת factory נפרדת שבה מיוצרים האובייקטים, שאחר כך מנוהלים על ידי החלק העיקרי של התוכנה.

מתי שוקלים להשתמש בדפוס ה-factory?

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

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

class CarOrder
{
  protected $carOrders = array();
  protected $car;
  
  // Order & make the car in the same method???!
  public function order($model=null)
  {
    if(strtolower($model) == 'r')
      $this->car = new CarModelR;
    else
      $this->car = new CarModelS;
 
      $this->carOrders[] = $this->car->model;
  }
  
  public function getCarOrders()
  {
    return $this->carOrders;
  }
}

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

מתודת Factory

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

  • מתודה order שתפקידה להזמין מכוניות.
  • מתודה make שתפקידה לייצר מכוניות.

class CarOrder
{
  protected $carOrders = array();
  protected $car;
  
  // Order the car.
  public function order($model=null)
  {
    $car = $this->make($model);
    $this->carOrders[] = $car->model;
  }
  
  // The actual making of the car is now separated into a method of its own.
  protected function make($model=null)
  {
    if(strtolower($model) == 'r')
      return $this->car = new CarModelR;
 
    return $this->car = new CarModelS;
  }
  
  public function getCarOrders()
  {
    return $this->carOrders;
  }
}

מה שמראה הדוגמה זה הפרדה של החלק שיוצר את האובייקטים אל מתודה נפרדת ששמה make. כשמייחדים ליצירת האוביקטים מתודה נפרדת משתמשים בגישה של factory method. השימוש ב-factory method הוא לגיטימי, ובהחלט יכול לעזור במקרים מסויימים, אבל לא בכל המקרים. בואו נראה מהם המגבלות של שימוש בגישה של factory method:

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

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

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

מחלקת Factory

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

class CarFactory {
 
  protected $car;
  
  // Determine which model to manufacture, and instantiate the concrete classes that make each model.
  public function make($model=null)
  {
    if(strtolower($model) == 'r')
      return $this->car = new CarModelR;
  
    return $this->car = new CarModelS;
  }
}

carFactory, המחלקה שהתמחותה ביצירת האובייקטים של המכוניות היא העיקר ב-factory design pattern, מפני שמה שהיא מאפשרת לנו לעשות זה לייצר אובייקטים בתוך מחלקה שתפקידה היחיד בעולם הוא להיות מפעל שמייצר אובייקטים, ובכך לפנות את המחלקה carOrder לעשות את העבודה היחידה שלשמה נוצרה, וזה הזמנת מכוניות.

הבנת הדוגמה יכולה להביא אותנו לנסח את את המאפיין החשוב ביותר של דפוס ה-Factory:

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

כדי להתחשב בשינוי, נוסיף למחלקה carOrder קונסטרקטור שייצור את האובייקט car$ מהמחלקה CarFactory, ונשנה את המתודה order כדי שייצור המכוניות יעשה עכשיו ב-carFactory.

class CarOrder
{
  protected $carOrders = array();
  protected $car;
  
  // First, create the carFactory.
  public function __construct()
  {
    $this->car = new CarFactory();
  }
  
  public function order($model=null)
  {
    // Now, one can use the method make() from the carFactory to manufacture the cars in a specialized class.
    $car = $this->car->make($model);
  $this->carOrders[]=$car->getModel();
  }
  
  public function getCarOrders()
  {
    return $this->carOrders;
  }
}

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

המחלקות עצמם

אבל מה לגבי ייצור האובייקטים עצמם או מה ההבדל בין מכונית מודל s לבין מכונית מודל r? פה אנחנו יכולים להיעזר ב-interface, וביישום שלו.

נתחיל מה-interface שמגדיר את פרטי הייצור עבור כל מכונית שתצא משערי המפעל:

interface Car
{
  public function getModel();
  
  public function getWheel();
  
  public function hasSunRoof();
}

ועכשיו אפשר לכתוב את המחלקות CarModelS ו-CarModelR שמיישמות את ה-interface.

נכתוב את המחלקה שמיישמת את ה-interface כדי לייצר את מודל s:

class CarModelS implements Car
{
  protected $model = 's';
  protected $wheel = 'sports';
  protected $sunRoof = true;
  
  public function getModel()
  {
    return $this->model;
  }
  
  public function getWheel()
  {
    return $this->wheel;
  }
  
  public function hasSunRoof()
  {
    return $this->sunRoof;
  }
}

נכתוב את המחלקה שמיישמת את ה-interface כדי לייצר את מודל r:

class CarModelR implements Car
{
  protected $model = 'r';
  protected $wheel = 'regular';
  protected $sunRoof = false;
  public function getModel()
  {
    return $this->model;
  }
  
  public function getWheel()
  {
    return $this->wheel;
  }
  
  public function hasSunRoof()
  {
    return $this->sunRoof;
  }
}

נבדוק את הקוד שכתבנו

נשתמש בקוד הבא כדי לבדוק את הקוד שכתבנו:

$carOrder = new CarOrder;
var_dump($carOrder->getCarOrders());
 
$carOrder->order('r');
var_dump($carOrder->getCarOrders());
 
$carOrder->order('s');
var_dump($carOrder->getCarOrders());

והתוצאה:

array (size=0)
  empty
 
array (size=1)
  0 => string 'r' (length=1)
 
array (size=2)
  0 => string 'r' (length=1)
  1 => string 's' (length=1)

סיכום

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

 

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

eBook cover The essentials of Object Oriented PHP

 

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

 

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

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

 

 

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

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

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

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

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

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

 

 

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

איך קוראים בעברית לצ`ופצ`יק של הקומקום?