מידע מורחב

  • תאריך
  • שעה 21:17
  • ע"י
  • צפיות 2810
  • תגובות 2
  • דירוג 5 /5
 

Design Patterns - תבניות עיצוב ב- PHP

Jul02
תבניות הוצגו לראשונה לעולם ע"י Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, אשר יחדיו היוו צוות הנקרא "כנופיית הארבע" ("Gang of Four"). מתכנתים אלו הבחינו כי ישנן מספר תבניות קוד שחוזרות על עצמן.

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

מה זה תבניות עיצוב ?

בהנדסת תוכנה, תבנית עיצוב (באנגלית: Design pattern) היא פתרון כללי לבעיה שכיחה בעיצוב תוכנה. תבנית עיצוב אינה עיצוב סופי שניתן להעבירו הישר לקוד, אלא תיאור או תבנית לדרך לפתרון בעיה, שעשויה להיות שימושית במצבים רבים. תבניות עיצוב מונחות עצמים מציגות לרוב יחסים וקשרי גומלין בין מחלקות או אובייקטים, בלי לפרט את המחלקות או אובייקטי היישום הסופיים המעורבים. אלגוריתמים אינם נחשבים כתבנית עיצוב, כיוון שהם פותרים בעיות חישוביות ולא בעיות עיצוב.[1]

מקור: ויקיפדיה


Singleton

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

class DB {

	/**
	*
	* Return DB instance or create intitial connection
	*
	* @return object (PDO)
	*
	* @access public
	*
	*/
	public static function getInstance() {
	
	if (!self::$instance)
	    {
	    self::$instance = new PDO("mysql:host='localhost';dbname='animals'", 'username', 'password');;
	    self::$instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	    }
	return self::$instance;
	}
	
	/*** Declare instance ***/
	private static $instance = NULL;
	
	/**
	*
	* the constructor is set to private so
	* so nobody can create a new instance using new
	*
	*/
	private function __construct() {
	  /*** maybe set the db name here later ***/
	}
	
	/**
	*
	* Like the constructor, we make __clone private
	* so nobody can clone the instance
	*
	*/
	private function __clone(){
	}

} /*** end of class ***/

בואו נבחן את הקוד הנ"ל. יצרנו מחלקה הנקראת DB ובתוכה ישנו משתנה instance$ כאשר הוא פרטי ולא ניתן לגישה חיצונית. בנוסף יצרנו פונקציות קונסטרקטור ו- clone גם כן פרטיות בכדי למנוע יצירה של המחלקה ע"י new ובכדי למנוע שכפול של האובייקט.
הפונקציה המרכזית היא ()getInstance שהיא פומבית וסטטית. בפונקציה זו אנו מבצעים בדיקה האם קיים כבר חיבור לבסיס הנתונים (instance) ואם לא אז היא יוצרת אחד ומחזירה אותו. במידה והיה קיים אז היא פשוט מחזירה אותו ישירות.
השגנו בדיוק את המטרה, ישנו רק חיבור אחד לבסיס הנתונים במהלך הריצה, כאשר מכל מקום בקוד אנו יכולים לבקש את החיבור הזה ללא חשש. אם לא היתה בקשה לחיבור החיבור לא מבוצע והמשאבים נשארים חופשיים.

להלן דוגמא איך ניתן להשתמש בזה בתוכנית:
<?php
try    {
    /*** query the database ***/
    $result = DB::getInstance()->query("SELECT animal_type, animal_name FROM animals");

    /*** loop over the results ***/
    foreach($result as $row)
        {
        print $row['animal_type'] .' - '. $row['animal_name'] . '';
        }
    }
catch(PDOException $e)
    {
    echo $e->getMessage();
    }
?>



Factory

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

/**
 *
 * @config interface
 */
interface Config
{
  function getName();
}


/**
 *
 * @config class
 *
 */
class userConfig implements Config{

/*
 * @username
 */
 public $user_id;
 /*** contructor is empty ***/
 public function __construct( $id ){
    $this->user_id = $id;
 }

 public static function Load( $id ) {
    return new userConfig( $id );
 }

 public function getName(){

    try
    {
        /*** query the database ***/
        $sql = "SELECT username FROM user_table WHERE user_id=:user_id";
        $db = db::getInstance();
        $stmt = $db->prepare($sql)
        $stmt->bindParam(':user_id', $this->user_id, PDO::PARAM_INT);
        return $stmt->fetch(PDO::FETCH_COLUMN);    
    }
    catch(PDOException $e)
    {
        /*** handle exception here ***/
        return false;
    }
 }

} /*** end of class ***/

/*** returns instance of config ***/
$conf = userConfig::Load( 1 );
echo $conf->getName();

?> 

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


Observer

Observer (צופה) היא תבנית הדומה בצורתה ל- Factory כאשר היא מציעה פתרון לשחרור האחיזה בין האובייקטים. כמו שמרמז שמו תבנית זו מכילה 2 אובייקטים, צופה ונצפה. הרעיון הוא יצירת אובייקט שיעשה פעולה כלשהי והעברתו לאובייקט הצופה. כאשר האובייקט הצופה מופעל הוא מפעיל גם את האובייקט הנצפה ובכך בעצם אנו משיגים "האזנה" לפעולות מסוימות.
<?php
interface myObserver{
    function onChanged( $sender, $args );
}


interface myObservable{
    function addObserver( $observer );
}


class CartItem implements myObservable{

 /*
  * @observer array
  */
 private $cart_observers = array();

 public function addItem( $name ){
    foreach( $this->cart_observers as $obs )
    {
        $obs->onChanged( $this, $name );
    }
 }

 /*** add observer to observer array ***/
 public function addObserver( $observer ){
    $this->cart_observers []= $observer;
 }

} /*** end of class ***/


class CartItemLogger implements myObserver {

 public function onChanged( $sender, $args ){
    /*** code to log item ***/
    echo( "Logging: $args added to cart log" );
 }

} /*** end of class ***/

$cart = new CartItem();
/*** when the observer is added, it listens for a signal ***/
$cart->addObserver( new CartItemLogger() );
$cart->addItem( "book" );
?>

בדוגמא זו אנו מאתחלים משתנה מסוג CartItem שיהוו את הצופה ומעבירים לו משתנה מסוג CartItemLogger שיהיה הנצפה. כעת עבור פעולת ()addItem של הצופה, תקרא גם פעולת ה- ()onChanged של ה- CartItemLogger.
עדיין לא נרשמת לאתר ? לחץ כאן להרשמה מהירה
נא המתן...
דירוג
  1. אחלה כתבה, גם ה visitor הוא תבנית שימושית מאוד :)
  2. כמובן שיש עוד לא מעט תבניות שימושיות שלא פירטתי פה. אם בא לך אשמח אם תכתוב על זה הסבר


עליך להתחבר כדי להגיב. לחץ כאן להתחברות.