الگوهای طراحی – نمونه اولیه – انجمن DEV

یکی از اولین چیزهایی که در برنامه نویسی یاد می گیریم، هر زبانی که باشد، نحوه کپی کردن یک مقدار از یک متغیر به متغیر دیگر است. یک فعالیت بسیار رایج اما موقعیت های جالبی را در زندگی روزمره ما به ارمغان می آورد.
تعریف
این یک الگوی ایجادی است که امکان ایجاد اشیاء جدید را با کپی کردن یک شیء موجود فراهم میآورد.
مثال مفهومی
وقتی برای اولین بار این الگوی طراحی را مطالعه کردم، اعتراف می کنم که در درک کاربرد آن مشکل داشتم. در ابتدا برای من کمی غیر ضروری به نظر می رسید، ایجاد یک ساختار کامل برای ایجاد یک کپی ساده. اما آن موقع بود که به استفاده احتمالی در روال خود فکر کردم.
تصور کنید که یک موجودیت Profile دارید که یکی از ویژگی های آن کلاس Configuration است و هر بار که یک Profile جدید ایجاد می کنید، باید به جدول تنظیمات بروید و آخرین تنظیمات را دریافت کنید… حالا تصور کنید، هر زمانی که نیاز دارید برای ایجاد یک شی جدید، یک پرس و جو از پایگاه داده ساخته خواهد شد!! اگر این داده ها مانند سایر اشیاء است که ما در حال دستکاری آنها هستیم، چرا از آن استفاده نکنیم؟! اینجاست که نمونه اولیه می درخشد و فرآیند کپی کردن این اشیاء را استاندارد می کند.
کپی کم عمق و کپی عمیق
حداقل در ابتدا، دو موقعیت برای فکر کردن در مورد کپی از اشیا وجود دارد، کپی جزئی (کپی کم عمق) و کپی عمیق (کپی عمیق). تفاوت در نحوه ارسال مقادیر از یک شی به شی دیگر است.
زمانی که برای اولین بار برنامه نویسی خواندم، زبان C در موسسه غالب بود (هنوز هم همینطور است) و مفهوم عبور مقادیر و ارجاعات بخشی از اولین کلاس ها بود، زیرا در این زبان بسیار رایج است. می دانم که اینطور نیست، من کمی در مورد آن توضیح خواهم داد.
پاساژ ارزش در مقابل مرجع
ارسال یک مقدار به معنای واقعی کلمه ارسال یک مقدار است، ما محتوای یک متغیر را به متغیر دیگر منتقل می کنیم، مانند مثال زیر، جایی که مقداری را که در a
برای b
. این بدان معناست که ما دو تخصیص حافظه، دو آدرس حافظه متفاوت داریم.
int a = 10;
int b = a;
بدیهی به نظر می رسد که اگر بخواهیم چیزی را کپی کنیم، می خواهیم مقدار آن را کپی کنیم، اما در برنامه نویسی انواع خاصی همیشه به عنوان مرجع ارسال می شوند، این مورد در مورد بردارها در C است.
int a[4] = [0,1,2,3];
int b[4] = a;
در این مورد، یک پاس با مرجع ساخته می شود، به این معنی که ما به b
فقط آدرس حافظه a
. این بدان معنی است که هر و همه تغییرات این بردار در هر دو منعکس خواهد شد a
همانطور که در b
.
اما این چه ربطی به کپی دارد؟!
به عنوان مثال، در چندین زبان، جاوا و سی شارپ، اشیا به عنوان مرجع ارسال می شوند و این منجر به رفتارهای عجیب و غریب می شود. هنگامی که ما یک شی را کپی می کنیم که دارای اشیاء دیگری است، این اشیاء داخلی به عنوان یک مرجع به مقصد خود کپی می شوند، بنابراین آنها همان حالت را در حافظه به اشتراک می گذارند، و مقادیر آنها همیشه بین کپی های آنها یکسان است، بنابراین یک کپی جزئی (کم عمق) کپی 🀄).
از کدوم استفاده کنم؟!
سود حاصل از استفاده از کپیهای جزئی، کارایی کد شما است، که نسبت به اجرای یک کپی عمیق، نمونهبرداری از یک شی داخلی جدید و کپی کردن مقادیر موجود، از منابع کمتری استفاده میکند.
اما کدام یک برای استفاده بهتر است؟ این به پروژه و نیاز شما بستگی دارد، الگوی Prototype استانداردسازی این فرآیندها را به شما می دهد، به طوری که هر زمان که نیاز به کپی کردن یک شی است، لازم نیست کلاس را باز کنید و بررسی کنید که آیا اشیاء داخلی بیشتری وجود دارد یا خیر. این اشیاء درونی اشیاء درونی دیگری ندارند…
اولین چیزی که به آن نیاز خواهیم داشت یک رابط Prototype است که متدها را داشته باشد ShallowCopy
ه DeepCopy
.
public interface IPrototype<T>
{
T ShallowCopy();
T DeepCopy();
}
اکنون میتوانیم کلاس انتزاعی خود را که از آن ارث میبرد پیادهسازی کنیم IPrototype
، در اینجا اگر بخواهیم می توانیم رابط را پیاده سازی کنیم، اما در این مورد ترجیح می دهم از یک کلاس متوسط استفاده کنم.
من هم کلاس را ایجاد خواهم کرد Weapon
برای اینکه کلاس داخلی ما باشد، من قبلاً آن را مستقیماً پیادهسازی میکنم و بنابراین ویژگیهای آن را بهعنوان انتزاعی علامتگذاری نمیکنم.
public abstract class Enemy : IPrototype<Enemy>
{
public string Name;
public string Description;
public Weapon Weapon;
public abstract Enemy ShallowCopy();
public abstract Enemy DeepCopy();
}
public class Weapon : IPrototype<Weapon>
{
public string Type;
public int Damage;
public Weapon(string type, int damage)
{
this.Type = type;
this.Damage = damage;
}
public Weapon ShallowCopy()
{
return (Weapon)this.MemberwiseClone();
}
public Weapon DeepCopy()
{
return new Weapon(Type, Damage);
}
}
توجه داشته باشید که روش های به ارث رسیده از
IPrototype
آنها به عنوان انتزاعی علامت گذاری شده اند تا در ارث بعدی کدگذاری شوند.
حالا بالاخره میتوانیم کلاس محصول خود، کلاس Troll را پیادهسازی کنیم.
public class Troll : Enemy
{
public override Enemy ShallowCopy()
{
return (Enemy)this.MemberwiseClone();
}
public override Enemy DeepCopy()
{
Enemy enemy = ShallowCopy();
enemy.Weapon = new Weapon(Weapon.Type, Weapon.Damage);
return enemy;
}
public override string ToString()
{
var Str = new StringBuilder();
Str.AppendLine($"Name: {this.Name}");
Str.AppendLine($"Description: {this.Description}");
Str.AppendLine($"Weapon: {Weapon.Type} ({Weapon.Damage})");
return Str.ToString();
}
}
میتوانیم ببینیم که کلاس Troll دو نوع کپی ما را پیادهسازی میکند و در حال حاضر در اجرای آن میفهمیم که مهم نیست چقدر مقادیر آنها شبیه به هم باشند، رفتار متفاوتی بین این دو وجود خواهد داشت.
برگزاری کلاس های ما
Enemy Boss = new Troll
{
Name = "Boss",
Description = "Tordoaldo is an evil troll, with a sharp intelligence and hidden strength, who lives in a forest cave adorned with treasures from his adventures.",
Weapon = new Weapon("Sword", 500)
};
Enemy Boss_ShallowCopy = Boss.ShallowCopy();
Enemy Boss_DeepCopy = Boss.DeepCopy();
System.Console.WriteLine("First Run \n");
ConsoleInfo();
Boss.Name = "BossWithNewName";
Boss.Weapon.Type = "Other Sword";
System.Console.WriteLine("Second Run \n");
ConsoleInfo();
void ConsoleInfo()
{
Console.WriteLine("Boss");
Console.WriteLine(Boss.ToString());
Console.WriteLine("Boss Shallow Copy");
Console.WriteLine(Boss_ShallowCopy.ToString());
Console.WriteLine("Boss Deep Copy");
Console.WriteLine(Boss_DeepCopy.ToString());
}
گام به گام
مرحله 1
در اینجا ما هر دو کپی را ایجاد می کنیم و می بینیم که همه ویژگی ها همانطور که باید باشند یکسان هستند.
گام 2
پس از آن نام “رئیس” اصلی خود را به روز کردیم و نام سلاح او را تغییر دادیم و آنچه در خروجی می بینیم جالب است!
همانطور که می بینیم “رئیس” ما با نام جدید خود بدون هیچ مشکلی ظاهر می شود. اما «رئیس کپی کم عمق» ما هم اسلحه اش را عوض کرده بود! این دقیقاً به دلیل خاصیتی است که قبلاً به آن اشاره کردیم، زیرا ما یک کپی جزئی، کلاس داخلی را ایجاد می کنیم Weapon
فضای یکسانی را در حافظه به اشتراک میگذارد، بنابراین هر تغییری که در آنجا ایجاد شود در دو شی «Boss» و «Boss Shallow Copy» منعکس میشود.
چه موقع باید استفاده کرد؟!
کتاب الگوهای اریش گاما سه مورد استفاده برای این مدل را فهرست میکند:
- وقتی کلاس هایی که نمونه سازی می شوند در زمان اجرا مشخص می شوند، برای مثال با بارگذاری پویا.
- برای جلوگیری از جفت شدن کد ما، از ساختن یک سلسله مراتب کلاس کارخانه به موازات سلسله مراتب کلاس محصول اجتناب کنیم.
- زمانی که نمونه های یک کلاس می توانند یکی از چند ترکیب مختلف حالت را داشته باشند. ممکن است بهتر باشد که تعداد متناظری از نمونه های اولیه را نصب کنید و آنها را شبیه سازی کنید، به جای نمونه سازی کلاس به صورت دستی، هر بار با وضعیت مناسب.
فواید
هنوز هم با استفاده از کتاب فوق، مزایایی داریم:
- افزودن و حذف محصولات در زمان اجرا این یک الگوی انعطافپذیرتر از سایر الگوهای ایجاد است، به شما امکان میدهد محصولات جدید را با نمونهسازی یک نمونه اولیه روی مشتری ثبت کنید، به این ترتیب مشتری میتواند نمونههای اولیه را در زمان اجرا نصب و حذف کند.
- اشیاء جدید را با مقادیر مختلف مشخص کنید. با استفاده از الگوی نمونه اولیه می توانیم تعداد کلاس های درگیر را به میزان قابل توجهی کاهش دهیم.
- با تغییر ساختار اشیاء جدید را مشخص کنید. این طراحی به ما اجازه می دهد تا اشیایی را با قطعات و زیربخش ها ایجاد کنیم، تا زمانی که شی کامپوزیت ما یک کلون عمیق را پیاده سازی کند.
عیب
نقطه ضعف اصلی این است که هر زیر کلاس Prototype باید عملیات Clone را پیاده سازی کند که می تواند دشوار باشد. مواردی که کلاس ها دارای مراجع دایره ای هستند یا از عملیات کپی پشتیبانی نمی کنند، نمونه هایی از این مشکل هستند.
مخزن GIT
الگوهای طراحی
مواد مطالعه
به پایان مقاله خود می رسیم و می خواستم مطالبی را که برای مطالعه این الگوی نرم افزار استفاده کردم را بگذارم:
گورو بازسازی
فاکتوری
الگوهای طراحی: راه حل های نرم افزاری شی گرا قابل استفاده مجدد