הפרדת חלקי התוכנה על בסיס מודל, מבט ובקר (Model-View-Controller)

הפרדת חלקי התוכנה על בסיס מודל, מבט ובקר (Model-View-Controller)

  • פרסומת

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

הבעיה

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

מודל (Model)

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

מבט (View)

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

בקר (Controller)

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

יישום התבנית (MVC) באתרי אינטרנט

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

יישום הרעיון עם ASP.NET

מייקרוסופט החלה ליישם את רעיון ה- MVC כבר בשלבים מוקדמים של ASP.NET עם Web Forms. לאחר מכן, מייקרוסופט הציגה גירסה ״קלה״ יותר של הרעיון תחת השם MVC (קצת מבלבל, אני יודע). הבקר נכתב בשפת #C, וכך גם המודל. לעמותם, המבט נכתב על ידי שילוב בין HTML לבין ASP.

יישום הרעיון עם PHP

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

דוגמה בשפת PHP

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

המודל

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

<?php 
class WebUser {
    
    private $first_name; 
    private $last_name; 
    
    public function __construct( $user_id ) 
    { 
        // Fetch data from database 
    } 
    
    public function get_first_name() 
    { 
        return $this->first_name;
    }
    
    public function get_last_name()
    {
        return $this->last_name;
    }
}

המבט

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

<h1>פרופיל משתמש</h1>
<table>
    <tbody>
        <tr>
            <th>שם פרטי</th>
            <td><?php echo $this->web_user->get_first_name(); ?></td>
        </tr>
        <tr>
            <th>שם משפחה</th>
            <td><?php echo $this->web_user->get_last_name(); ?></td>
        </tr>
    </tbody>
</table>

הבקר

הבקר מכיל עותק של המודל (אובייקט של WebUser) ופונקציה שמייצרת פלט בצורת HTML הנקראת render(). בגלל שלכל רכיב באתר יש גם בקר וגם מבט, אפשר לתת לשניהם את אותו השם, ורק לשנות את הסיומת: Profile.php לבקר ו- Profile.phtml למבט. ניתן לרכז את כל הפונקציונליות הזו לעצם אחד שממנו יירש כל עצם אחר באתר. לעצם הזה נקרא Controller והוא יוגדר כ-abstract. הפונקציה get_script_path() מחפשת את הקובץ שמכיל את המבט על ידי חיפוש של השם של הקובץ בו מוגדר הבקר, רק סיומת של .phtml.

<?php 
abstract class Controller 
{ 
    /** 
     * Render the template with the local properties. 
     * @return string The rendered template. 
     */ 
    public function render( $echo = false )
    { 
        $rendered_image = ''; 
        if( file_exists( $this->get_script_path() ) ) 
        {
            ob_start();
            include( $this->get_script_path() );
            $rendered_image = ob_get_clean();
        }
        
        if( !$echo )
        {
            return $rendered_image;
        }
        echo $rendered_image;
    }
    
    /**
     * Get the full path to the template file.
     * @return string The full path.
     */
    private function get_script_path()
    {
        if( !isset( $this->script_path ) )
        {
            $class_name =  substr( get_called_class() , strrpos( get_called_class(), '\\') + 1);
            $this->script_path = $this->get_dir() . '/' . $class_name . '.phtml';
        }
        return $this->script_path;
    }
    
    /**
     * Get the directory of the file where the derived class is defined.
     * @return string The directorty to the file.
     */
    protected function get_dir() 
    {
        $rc = new \ReflectionClass(get_class($this));
        return dirname($rc->getFileName());
    }
}

כעת ניתן ליצור את הבקר באמצעות הקוד הבא:

<?php 
class Profile extends Controller 
{
    private $web_user;
    
    public function __construct() 
    { 
        $this->web_user = new WebUser( /* user id */ );
    }
}

בשביל ליצור פלט של פרופיל המשתמש, הקוד הבא יספיק:

<?php 
$profile = new Profile(); 
$profile->render(true);

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