תבנית Prototype

בהנדסת תוכנה, תבנית ProtoType היא תבנית עיצוב, שנועדה למקרים בהם מעוניינים ליצור אובייקט כהעתק של אובייקט נתון, אשר טיפוסו ידוע רק בזמן ריצה. התבנית שימושית במקרים בהם מקבלים מצביע לאובייקט נתון, ורוצים ליצור העתק שלו. מאחר שאין פתרון פשוט לבעיה (בירור הטיפוס וניסיון לגשת לפונקציית בנאי ההעתקה שלו צורכים זמן ומשאבים), אנו זקוקים לתבנית Prototype. התבנית מונעת כתיבה של תת-מחלקה שמטרתה ייצור האובייקט (כמו שקורה בתבנית Factory Method), וכן חוסכת את המשאבים הדרושים ליצירת אובייקט בדרך המסורתית (שימוש ב-new). לתבנית שני יתרונות עיקריים: ניצול פולימורפיזם – הקוד בצד ה-Client לא תלוי בטיפוסים השונים הנגזרים ממחלקת הבסיס. בזמן הידור הפונקציה clone תחזיר העתק של האובייקט הנתון. יעילות – העתקת העצם מבוצעת ישירות, ללא שימוש בשאילתא לבירור הטיפוס של האובייקט, אשר צורכת משאבי זמן ומקום. חסרונות: ממשק אחיד – מאחר שהממשק שלה חייב להיות אחיד, לא ניתן לגוון ביכולות. כך למשל, לא ניתן להעביר לפונקציה clone פרמטרים שונים בהתאם לטיפוסים השונים (לבעיה הזו יש פתרון לפיו נדרשת פונקציה נוספת set אשר מאתחלת את הפרמטרים). אופן העתקת העצמים – ישנו קושי בהחלטה כיצד להעתיק את העצם (Deep copy או Shallow copy). על מנת לממש את התבנית, נגדיר מחלקה בסיסית מופשטת אשר מכילה מתודה clone (יכולה גם להיות ממשק). כל מחלקה אשר צריכה שלמחלקת הבסיס תהיה היכולת ליצור העתק בזמן ריצה של עצמה תממש את המתודה clone. בצד ה-Client, במקום לרשום קוד שמשתמש ב-new וכן רושם "hard-coded" את שם המחלקה, פשוט נקרא למתודה clone, אשר תיצור עבורנו את האובייקט מהטיפוס המבוקש. /** * Prototype Class */ public class Cookie implements Cloneable { @Override public Cookie clone() { Cookie copy; try { copy = (Cookie) super.clone(); } catch (CloneNotSupportedException unexpected) { throw new AssertionError(unexpected); } //In an actual implementation of this pattern you might now change references to //the expensive to produce parts from the copies that are held inside the prototype. return copy; } } /** * Concrete Prototypes to clone */ public class CoconutCookie extends Cookie { } /** * Client Class */ public class CookieMachine { private Cookie cookie; // Could have been a private Cloneable cookie. public CookieMachine(Cookie cookie) { this.cookie = cookie; } public Cookie makeCookie() { return (Cookie) cookie.clone(); } public static void main(String args[]) { Cookie tempCookie = null; Cookie prot = new CoconutCookie(); CookieMachine cm = new CookieMachine(prot); for (int i = 0; i < 100; i++) tempCookie = cm.makeCookie(); } } public abstract class Cookie { protected string Color { get; set; } public Cookie Clone() { return (Cookie)this.MemberwiseClone(); // Create a shallow copy } public override string ToString() { return string.Format("{0} Cookie", this.Color); } } public class ChocolateCookie : Cookie { public ChocolateCookie() { this.Color = "Brown"; } } public class WhiteChocolateCookie : Cookie { public WhiteChocolateCookie() { this.Color = "White"; } } public class CookieMachine { private Cookie cookie; public CookieMachine(Cookie cookieToClone) { this.cookie = cookieToClone; } public Cookie MakeCookie() { return cookie.Clone(); } } class Program { static void Main(string[] args) { Cookie tempCookie = null; Cookie prototype = new WhiteChocolateCookie(); CookieMachine cookieMachine = new CookieMachine(prototype); for (int i = 0; i < 10; i++) tempCookie = cookieMachine.MakeCookie(); } } ב-Java, מחלקת הבסיס, Object, כוללת את המתודה clone, אשר מחזירה מצביע מסוג Object למופע הנוכחי. מאחר ש-Object הוא הבסיס לכל המחלקות, מסופק ממשק ProtoType לכל העצמים ב-Java. גם ב-#C יש תמיכה בתבנית ProtoType כבר במחלקת הבסיס: System.Object, אולם כאן קיימת ההבחנה בין העתקה רדודה (MemberwiseClone) להעתקה עמוקה (Clone). אם אובייקט רוצה להשתמש ב-clone, עליו לממש את הממשק ICloneable, תוך מימוש הפונקציה clone שלו. מאיר סלע, Design Patterns – חשיבה תבניתית במערכות מונחות עצמים