ProtoType היא תבנית עיצוב מסוג Creational המאפשרת ליצור אובייקט כשיבוט של אובייקט נתון, במילים אחרות זה מאפשר לנו ליצור מופע חדש על ידי העתקת מופעים קיימים.
זה יכול לקרות בזמן ריצה ומבלי שהקוד שלך יהיה תלוי במחלקות שלהם.
תוכן עניינים
הבעיה
ב-C#, מחלקות הן מסוג reference, כך שאם תעתיק את אובייקט המחלקה לאובייקט ריק אחר ותשנה את האובייקט האחד אז זה ישפיע גם על האובייקט השני.
נניח שיש לך אובייקט, ואתה רוצה ליצור עותק מדויק שלו. איך היית עושה את זה?
- ראשית, עליך ליצור אובייקט חדש מאותה מחלקה.
- לאחר מכן עליך לעבור על כל השדות של האובייקט המקורי ולהעתיק את ערכיהם לאובייקט החדש.
על פניו נשמע פשוט, אבל יש פה קאץ’. לא את כל האובייקטים ניתן להעתיק באופן הזה, מהסיבה הפשוטה שחלק מהשדות של האובייקט עשויים להיות בסיווג Private וכמובן אינם נראים מחוץ לאובייקט עצמו.
ישנה בעיה נוספת. בשביל ליצור עותק אנו צריכים להכיר את סוג האובייקט, דבר שגורם לקוד לתלות במחלקה של האובייקט.
בנוסף יש לא מעט מקרים שאתה מכיר רק את הממשק שהאובייקט עוקב אחריו, אך לא את האובייקט עצמו, גם כאן נהיה מוגבלים.
הפתרון
התבנית מכריזה על interface משותף לכל האובייקטים התומכים בשכפול.
ממשק זה מאפשר לך לשכפל אובייקט מבלי להצמיד את הקוד שלך למחלקה של אותו אובייקט. בדרך כלל, ממשק כזה מכיל רק שיטת שכפול (Clone) אחת. מימוש שיטת השיבוט דומה מאוד בכל המחלקות וכל אחת מהמחלקות המממשות אותו – תממש את clone באופן המתאים לה.
אובייקט המקור התומך בשכפול נקרא אב טיפוס וממנו ניצור שכפולים לפי הדרישות שלנו.
יתרונות
- ניצול של תכונת הפולימורפיזם – הקוד בצד ה-Client לא תלוי בטיפוסים השונים הנגזרים ממחלקת הבסיס. בזמן קומפילציה הפונקציה clone תחזיר העתק של האובייקט הנתון.
- מודולריות: אין תלות בטיפוסים הקונקרטיים
- התבנית מספקת חלופה להורשה כאשר לאובייקט יש הגדרות קבועות מראש עבור אובייקטים מורכבים.
- יעילות: העתקת העצם מבוצעת ישירות, ללא שימוש בשאילתא לבירור הטיפוס של האובייקט, אשר צורכת משאבי זמן ומקום.
חסרונות
- מכיוון שעל המתודה clone
להיות אחידה, אין אפשרות להעביר בה פרמטרים שונים, ובמקום זאת חייב להשתמש במתודות set לאחר שהאובייקט המועתק נוצר. - צריך להחליט האם לבצע העתקה רדודה (העתקת ביטים) או העתקה עמוקה אופן העתקת העצמים – וישנו קושי באיזה העתקה צריך לבחור (Deep copy או Shallow copy)..
UML class diagram
מימוש תבנית Prototype
בדוגמא הבאה אתן דוגמא לשימוש בתבנית Prototype, ניתן לצפות בהדוגמא גם בGitHub
public interface IEmployee
{
IEmployee Clone();
string GetDetails();
}
public class Lawyer : IEmployee
{
public string Name { get; set; }
public string Role { get; set; }
public string Court { get; set; }
public IEmployee Clone()
{
// Shallow Copy: only top-level objects are duplicated
return (IEmployee)MemberwiseClone();
// Deep Copy: all objects are duplicated
//return (IEmployee)this.Clone();
}
public string GetDetails() => $"{Name}, {Role}, {Court}";
}
public class Typist : IEmployee
{
public int WordsPerMinute { get; set; }
public string Name { get; set; }
public string Role { get; set; }
public IEmployee Clone()
{
// Shallow Copy: only top-level objects are duplicated
return (IEmployee)MemberwiseClone();
// Deep Copy: all objects are duplicated
//return (IEmployee)this.Clone();
}
public string GetDetails() => $"{Name}, {Role}, {WordsPerMinute}";
}
class Program
{
static void Main(string[] args)
{
Lawyer law = new()
{
Name = "'law' Full Name",
Role = "Traffic lawyer",
Court = "Haifa"
};
Lawyer law2 = law; // Create Reference of law
law.Name = "Amir Shtaiman"; //Change name of both law & law2
Lawyer lawClone = (Lawyer)law.Clone(); //Create clone of law
lawClone.Name = "Moshe the clone";
Console.WriteLine($"Origin: {law.GetDetails()}");
Console.WriteLine($"Origin Law2: {law2.GetDetails()}");
Console.WriteLine($"After Clone: {lawClone.GetDetails()}");
Typist typist = new()
{
Name = "Eyal Shani",
Role = "Typist",
WordsPerMinute = 120
};
Typist typistClone = (Typist)typist.Clone();
typistClone.Name = "Moshe the clone";
typistClone.WordsPerMinute = 69; //Not mention Role, it will copy above
Console.WriteLine($"\n\nOrigin: {typist.GetDetails()}");
Console.WriteLine($"After Clone: {typistClone.GetDetails()}");
Console.ReadKey();
}
}
בחלק הראשון יוצרים מופע של המחלקה ‘Lawyer’, נקרא למשתנה law
על מנת להמחיש את הצורך בתבנית יצרתי משתנה נוסף מאותו סוג ‘law2’ וביצעתי השמה מlaw1, לאחר מכן שיניתי את השם של המופע המקורי – שימו לב שהשם השתנה בשני המקומות וזה קרה מכיוון שב#C לא מבוצע שכפול אלא reference לאובייקט המקור.
לאחר מכן יצרתי העתקה על ידי השימוש בפונקציה המדוברת Clone וניתן לראות בהדפסות שבמקרה הזה נוצר שיבוט/העתק ולא reference של המקור.