زمان تخمینی مطالعه: 12 دقیقه
الگوی طراحی Prototype یک الگوی طراحی Creational است که امکان ایجاد اشیاء(object) جدید را با کپی کردن یک شی موجود فراهم میکند. الگوی Prototype به برنامه نویس امکان میدهد پیچیدگی ایجاد نمونههای جدید را از مشتری پنهان کند. در واقع مفهوم اصلی این الگو، کپی کردن یک شی موجود به جای ایجاد یک نمونه جدید از ابتدا است، فرآیندی که ممکن است بسیار پرهزینه باشد. شی موجود به عنوان یک نمونه اولیه عمل میکند و شامل وضعیت object است. این الگوی طراحی با نام کلون(Clone) نیز شناخته میشود.
بیان مسئله: فرض کنید یک Object دارید و میخواهید یک کپی دقیق از آن ایجاد کنید. نحوه انجام این کار چگونه است؟ برای انجام این کار ابتدا باید یک شی جدید از همان کلاس ایجاد کنید. سپس باید تمام فیلدهای شی اصلی را مرور کنید و مقادیر آنها را در شی جدید کپی کنید. با انجام این مراحل شاید تصور شود کار به خوبی پیش رفته است، اماهنوز یک مشکل دیگر وجود دارد. همه اشیاء را نمیتوان به این روش کپی کرد زیرا برخی از فیلدهای شی ممکن است خصوصی(Private) باشند و از خارج از خود Object قابل مشاهده و دسترسی نباشند.
یک مشکل دیگر در استفاده از رویکرد مستقیم وجود دارد. از آنجایی که برای ایجاد یک کپی باید کلاس شی را بشناسید، کد نوشته شده شما به آن کلاس وابسته میشود. اگر بوجود آمدن وابستگی بیشتر ایرادی نداشته باشد، با این حال یک مشکل دیگر باز وجود دارد. گاهی اوقات شما فقط اینترفیسی را میشناسید که شی از آن پیروی میکند و اطلاعی از کلاس آن ندارید، به عنوان مثال پارامتری در یک متد را در نظر بگیرید، این پارامتر هر Object ای را که از یک اینترفیس پیروی میکند میپذیرد .
الگوی Prototype فرآیند شبیهسازی را به اشیاء واقعی که در حال شبیهسازی هستند واگذار میکند. این الگو یک اینترفیس مشترک را برای تمامی اشیایی که از شبیهسازی پشتیبانی میکنند، اعلان میکند. این اینترفیس به برنامه نویس امکان میدهد یک شی را بدون وابسته کردن کد نوشته شده با کلاس آن شی، کلون کنید. معمولاً چنین اینترفیسی فقط شامل یک متد clone میشود. پیادهسازی متد clone (الگوی Prototype) در همه کلاسها بسیار شبیه است. در این تکنیک ابتدا متد، یک شی از کلاس فعلی ایجاد میکند سپس تمام مقادیر فیلد شی قدیمی را به شی جدید منتقل میکند. در طی این فرآیند حتی میتوانید فیلدهای خصوصی(Private) را کپی کنید زیرا اکثر زبانهای برنامهنویسی به اشیا اجازه میدهند به فیلدهای خصوصی دیگر اشیاء متعلق به همان کلاس دسترسی پیدا کنند.
بر اساس این مفاهیم Object ای که از شبیهسازی(Cloning) پشتیبانی میکند، Prototype نامیده میشود. هنگامی که اشیاء شما دارای دهها فیلد و صدها پیکربندی ممکن هستند، کلون کردن آنها ممکن است به عنوان جایگزینی برای طبقهبندی فرعی(subclassing) باشد.
در واقع در این الگو روند به این نحو است که شما مجموعهای از اشیاء را ایجاد میکنید که به روشهای مختلفی پیکربندی شدهاند. هنگامی که به یک شی مانند آنچه پیکربندی کردهاید نیاز دارید، به جای ساختن یک شی جدید از ابتدا، فقط یک Prototype از آن را کلون میکنید.
نمونه قابل قیاس در دنیای واقعی
در زندگی واقعی، Prototype ها برای انجام تستهای مختلف قبل از شروع تولید انبوه یک محصول استفاده میشود. با این حال، در این مورد خاص، Prototype در هیچ فرآیند تولید واقعی مشارکت نمیکند و یه جای آن نقشی منفعل را ایفا میکند.
از آنجایی که Prototype های صنعتی واقعاً خود را کپی نمیکنند، مثال بسیار نزدیکتر و شبیهتر به الگوی Prototype، فرآیند تقسیم سلولی میتوزی است (تصویر بالا). پس از تقسیم میتوزی، یک جفت سلول یکسان تشکیل میشود. سلول اصلی به عنوان نمونه اولیه(Prototype) عمل کرده و نقش فعالی در ایجاد کپی بر عهده دارد.
ساختار الگوی Prototype
در این بخش به بررسی ساختار الگوی prototype میپردازیم و دو نوع پیادهسازی آن را به صورت UML خواهیم دید.
– پیادهسازی پایه(Basic)
- گام 1: اینترفیس Prototype متدهای کلون کردن را اعلان میکند. در بیشتر موارد، آن یک متد clone تک است.
- گام 2: کلاس Concrete Prototype روش کلون را پیادهسازی میکند. علاوه بر کپی کردن دادههای شی اصلی در کلون، این روش ممکن است برخی از موارد لبهای(edge) فرآیند شبیهسازی(Cloning) مربوط به شبیهسازی اشیاء مرتبط، باز کردن وابستگیهای بازگشتی و غیره را نیز انجام دهد.
- گام 3: کلاینت میتواند یک کپی از هر شی که از اینترفیس Prototype پیروی میکند تولید کند.
– پیادهسازی Prototype رجیستری
- گام 1: روش Prototype Registry راه آسانی برای دسترسی به Prototype های پرکاربرد فراهم میکند. در واقع این روش مجموعهای از اشیاء از پیش ساخته شده را که آماده کپی هستند در خود ذخیره میکند. سادهترین Prototype Registry یک نقشه هش(Hash map) name → prototype است. با این حال، اگر به معیارهای جستجوی بهتری نسبت به یک نام ساده نیاز دارید، میتوانید نسخه بسیار قویتری از رجیستری را بسازید.
شبه کد(Pseudocode)
در مقال ذکر شده در ادامه الگوی Prototype به شما امکان میدهد کپیهای دقیقی از اجسام هندسی تولید کنید، بدون اینکه کد را به کلاسهای آنها وابسته کنید. برای فهم بیشتر شبه کد به UML زیر دقت کنید:
همه کلاسهای شکل از یک اینترفیس یکسان پیروی میکنند که یک متد شبیهسازی(Clone) را ارائه میدهد. یک زیر کلاس ممکن است قبل از کپی کردن مقادیر فیلد خود در شیء به دست آمده، روش شبیهسازی(Cloning) والد را فراخوانی کند. در ادامه به شبه کد توجه کنید:
// Base prototype.
abstract class Shape is
field X: int
field Y: int
field color: string
// A regular constructor.
constructor Shape() is
// ...
// The prototype constructor. A fresh object is initialized
// with values from the existing object.
constructor Shape(source: Shape) is
this()
this.X = source.X
this.Y = source.Y
this.color = source.color
// The clone operation returns one of the Shape subclasses.
abstract method clone():Shape
// Concrete prototype. The cloning method creates a new object
// in one go by calling the constructor of the current class and
// passing the current object as the constructor's argument.
// Performing all the actual copying in the constructor helps to
// keep the result consistent: the constructor will not return a
// result until the new object is fully built; thus, no object
// can have a reference to a partially-built clone.
class Rectangle extends Shape is
field width: int
field height: int
constructor Rectangle(source: Rectangle) is
// A parent constructor call is needed to copy private
// fields defined in the parent class.
super(source)
this.width = source.width
this.height = source.height
method clone():Shape is
return new Rectangle(this)
class Circle extends Shape is
field radius: int
constructor Circle(source: Circle) is
super(source)
this.radius = source.radius
method clone():Shape is
return new Circle(this)
// Somewhere in the client code.
class Application is
field shapes: array of Shape
constructor Application() is
Circle circle = new Circle()
circle.X = 10
circle.Y = 10
circle.radius = 20
shapes.add(circle)
Circle anotherCircle = circle.clone()
shapes.add(anotherCircle)
// The `anotherCircle` variable contains an exact copy
// of the `circle` object.
Rectangle rectangle = new Rectangle()
rectangle.width = 10
rectangle.height = 20
shapes.add(rectangle)
method businessLogic() is
// Prototype rocks because it lets you produce a copy of
// an object without knowing anything about its type.
Array shapesCopy = new Array of Shapes.
// For instance, we don't know the exact elements in the
// shapes array. All we know is that they are all
// shapes. But thanks to polymorphism, when we call the
// `clone` method on a shape the program checks its real
// class and runs the appropriate clone method defined
// in that class. That's why we get proper clones
// instead of a set of simple Shape objects.
foreach (s in shapes) do
shapesCopy.add(s.clone())
// The `shapesCopy` array contains exact copies of the
// `shape` array's children.
قابلیتها و کاربردها
- از الگوی Prototype زمانی استفاده کنید که کد شما نباید به کلاسهای concrete اشیایی که باید کپی کنید بستگی داشته باشد. این اتفاق زمانی که کد نوشته شده شما با اشیایی که از کدهای فردی دیگر از طریق برخی از اینترفیسها به شما ارسال میشود سر و کار دارد این اتفاق زیاد میافتد. کلاسهای concrete این اشیاء ناشناخته هستند و حتی اگر بخواهید نمیتوانید به آنها وابسته باشید. الگوی Prototype کد کلاینت را با یک اینترفیس کلی برای کار با تمام اشیایی که از شبیهسازی پشتیبانی میکنند، فراهم میکند. این اینترفیس کد کلاینت را از کلاسهای concrete اشیایی که شبیهسازی میکند مستقل میکند.
- زمانی که میخواهید تعداد زیرکلاسهایی را که فقط در نحوه مقداردهی اولیه اشیاء مربوطه خود متفاوت هستند، کاهش دهید، از الگو استفاده کنید. فرض کنید یک کلاس پیچیده دارید که قبل از استفاده از آن نیاز به یک پیکربندی پر زحمت دارد. چندین راه متداول برای پیکربندی این کلاس وجود دارد و این کد در برنامه شما پراکنده است. برای کاهش تکرار، چندین زیر کلاس ایجاد میکنید و هر کد پیکربندی مشترک را در سازنده(Constructor) آنها قرار میدهید. با این روش شما مشکل تکثیر را حل کردید، اما اکنون زیر کلاسهای ساختگی زیادی دارید. الگوی Prototype به شما امکان میدهد مجموعهای از اشیاء از پیش ساخته شده را که به روشهای مختلف به عنوان Prototype پیکربندی شدهاند را استفاده کنید. به جای تولید اولیه(instantiating) یک زیر کلاس که با برخی از تنظیمات مطابقت دارد، مشتری میتواند به سادگی به دنبال prototype مناسب باشد و آن را شبیهسازی کند.
نحوه پیادهسازی
- اینترفیس prototype را ایجاد کنید و متد clone را در آن اعلان کنید. یا فقط متد را به تمام کلاسهای سلسله مراتب کلاس موجود اضافه کنید، این در حالتی است اگر فقط یک مورد دارید.
- یک کلاس prototype باید سازنده جایگزینی را تعریف کند که شیء آن کلاس را به عنوان آرگومان میپذیرد. سازنده باید مقادیر تمام فیلدهای تعریف شده در کلاس را از شی ارسال شده متعلق به prototype کپی کند. اگر در حال تغییر یک کلاس فرعی هستید، باید سازنده والد را فراخوانی کنید تا به ابرکلاس(Superclass) اجازه دهید کلونینگ فیلدهای خصوصی خود را انجام دهد. اگر زبان برنامه نویسی شما از overloading متد پشتیبانی نمیکند، نمیتوانید یک سازنده «prototype» جداگانه ایجاد کنید. بنابراین کپی کردن دادههای شی در کلون جدید ایجاد شده، باید در متد clone انجام شود. با این حال، داشتن این کد در یک سازنده معمولی امنتر است، زیرا شی بهدستآمده دقیقاً پس از فراخوانی با اپراتور new، کاملاً پیکربندی شده برگردانده میشود.
- روش شبیهسازی معمولاً از در یک خط واحد نوشته میشود: اجرای یک اپراتور new با نسخه prototype سازنده. توجه داشته باشید که هر کلاس باید صراحتاً متد شبیهسازی را بازنویسی کند و از نام کلاس خود همراه با پراتور new استفاده کند. در غیر این صورت، روش شبیهسازی ممکن است یک شی از یک کلاس والد تولید کند.
- به صورت اختیاری، یک رجیستری prototype مرکزی برای ذخیره کاتالوگ نمونههای اولیه پر کاربرد ایجاد کنید. میتوانید رجیستری را بهعنوان یک کلاس Factory جدید پیادهسازی کنید یا آن را با یک متد استاتیک برای واکشی نمونه اولیه در کلاس نمونه اولیه قرار دهید. این روش باید یک نمونه اولیه را بر اساس معیارهای درخواستی جستجو کند که کد مشتری به متد ارسال میکند. معیارها ممکن است یک تگ رشته ساده یا مجموعه پیچیدهای از پارامترهای جستجو باشد. پس از یافتن نمونه اولیه مناسب، رجیستری باید آن را شبیهسازی کند و کپی را به مشتری برگرداند. در نهایت، فراخوانیهای مستقیم سازندههای کلاسهای فرعی را با فراخوانی به روش factory رجیستری نمونه جایگزین کنید.
مزایا و معایب الگوی Prototype
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- شما میتوانید اشیاء را بدون اتصال به کلاسهای concrete آنها شبیهسازی کنید.
- شما میتوانید از شر کد مقدار دهی مکرر به نفع شبیهسازی نمونههای اولیه از پیش ساخته شده خلاص شوید.
- شما میتوانید اشیاء پیچیده را راحتتر تولید کنید.
- هنگامی که با تنظیمات از پیش مشخص شده برای اشیاء پیچیده سروکار دارید، با این روش جایگزینی برای وراثت دریافت میکنید.
– معایب
- شبیهسازی اشیاء پیچیده که دارای ارجاعات حلقهای هستند ممکن است بسیار مشکل باشد.
ارتباط با الگوهای دیگر
- بسیاری از الگوها با استفاده از روش Factory (کمتر پیچیدهتر و قابل تنظیمتر از طریق زیر کلاسها) شروع میشوند و کم کم به سمت Abstract Factory، Prototype یا Builder (انعطافپذیرتر، اما پیچیدهتر) تکامل مییابند.
- کلاسهایAbstract Factory اغلب بر اساس مجموعهای از متدهای Factory هستند، اما شما همچنین میتوانید از Prototype برای ترکیب متدهای این کلاسها استفاده کنید.
- هنگامی که نیاز به ذخیره کپی از الگوی Command در تاریخچه دارید، نمونه اولیه میتواند به شما کمک کند.
- طرحیهایی که به شدت از Composite و Decorator استفاده میکنند اغلب میتوانند از نمونه اولیه بهره ببرند. استفاده از الگو به شما این امکان را میدهد که ساختارهای پیچیده را به جای بازسازی مجدد از ابتدا شبیهسازی کنید.
- الگوی Prototype مبتنی بر وراثت نیست، بنابراین اشکالات آن را ندارد. از سوی دیگر، Prototype به یک مقداردهی اولیه پیچیده از شی کلون شده نیاز دارد. Factory Method بر اساس وراثت است اما نیازی به مرحله اولیه سازی ندارد.
- گاهی اوقات Prototype میتواند جایگزین سادهتری برای Memento باشد. اگر شیء، وضعیتی که میخواهید در تاریخچه ذخیره کنید، نسبتاً ساده باشد و پیوندی به منابع خارجی نداشته باشد، یا پیوندها به راحتی دوباره برقرار شوند، کار میکند.
- Abstract Factory، Builder و نمونههای اولیه همگی میتوانند بهعنوان Singletons پیادهسازی شوند.
2 پاسخ
با سلام
عالی
با سلام و عرض ادب ممنونم به خاطر توجه جنابعالی به مطالب سایت الکتروهایو