Prototype Pattern
Prototype Pattern هو نمط تصميم إبداعي يُستخدم لإنشاء كائنات جديدة عن طريق نسخ الكائنات الموجودة بدلاً من إنشائها من البداية. يكون ذلك مفيدًا عندما يكون إنشاء الكائنات مكلفًا أو معقدًا أو عندما تكون هناك حاجة إلى العديد من الكائنات المتشابهة مع اختلافات بسيطة.
في البرمجة كائنية التوجه، يسمح Prototype Pattern للكائن بالعمل كقالب لإنشاء كائنات أخرى. بدلاً من مطالبة factory أو constructor ببناء كائن جديد خطوة بخطوة، يقوم التطبيق بعمل clone لكائن موجود ثم يعدّل الأجزاء التي تحتاج إلى التغيير فقط.
مقدمة
في المقالات السابقة من سلسلة Design Patterns، ناقشنا Factory Pattern، وAbstract Factory Pattern، وBuilder Pattern. كلها أنماط تصميم إبداعية، لكن كل واحدة منها تحل مشكلة إنشاء كائن مختلفة.
يقرر Factory Pattern الكائن الذي يجب إنشاؤه. يقوم Abstract Factory Pattern بإنشاء عائلات من الكائنات ذات الصلة. يقوم Builder Pattern ببناء كائنات معقدة خطوة بخطوة. يقوم Prototype Pattern بإنشاء كائنات جديدة عن طريق نسخ كائن موجود.
يكون هذا النمط مفيدًا بشكل خاص عندما تتطلب تهيئة الكائن العديد من القيم أو بيانات قاعدة البيانات أو التكوين أو استجابات API أو منطق الإعداد الباهظ الثمن. من خلال عمل cloning لكائن جاهز، يمكن للتطبيق تجنب تكرار نفس عملية الإنشاء عدة مرات.
ما هو Prototype Pattern؟
Prototype Pattern هو نمط تصميم يقوم بإنشاء كائنات من خلال cloning لكائن موجود، يسمى prototype. يصبح الكائن cloned مثيلًا جديدًا بنفس الحالة الأولية للكائن الأصلي.
بعبارات بسيطة، prototype عبارة عن عينة جاهزة. عندما يحتاج التطبيق إلى كائن مماثل، فإنه ينسخ العينة ويخصص النسخة.
على سبيل المثال، قد يستخدم محرر المستندات قالب فاتورة prototype. عند الحاجة إلى فاتورة جديدة، يقوم النظام clone بإدخال قالب الفاتورة وتغيير تفاصيل العميل ورقم الفاتورة والتواريخ.
الفكرة الرئيسية لـ Prototype Pattern
تعتمد الفكرة الرئيسية لـ Prototype Pattern على النسخ بدلاً من البناء. قد يكون هذا مفيدًا عندما يتطلب إنشاء كائن جديد من الصفر خطوات إعداد متكررة.
يتضمن النمط عادةً ما يلي:
- كائن prototype الذي يحتوي بالفعل على القيم الافتراضية أو التكوين المجهز.
- آلية cloning لإنشاء نسخة من الكائن.
- تخصيص اختياري لكائن cloned بعد إنشائه.
يمكن لهذا الأسلوب أن يجعل عملية إنشاء الكائنات أسرع وأبسط وأكثر اتساقًا في مواقف معينة.
لماذا يعد Prototype Pattern مهمًا
يعد Prototype Pattern مهمًا لأن إنشاء الكائن قد يكون مكلفًا أو متكررًا في بعض الأحيان. تتطلب بعض الكائنات إعدادًا معقدًا أو العديد من معلمات constructor أو كائنات متداخلة أو تكوينات افتراضية.
إذا تم تكرار نفس الإعداد عدة مرات، يصبح من الصعب الحفاظ على الكود. يسمح Prototype Pattern للمطورين بإعداد كائن مرة واحدة وإعادة استخدامه كقالب.
يمكن أن يؤدي ذلك إلى تحسين الأداء في بعض الحالات وتحسين تنظيم الكود عندما تشترك العديد من الكائنات في نفس البنية الأساسية.
مشكلة بدون Prototype Pattern
تخيل تطبيقًا يقوم بإنشاء قوالب التقارير. يحتوي كل تقرير على عنوان وتخطيط وأقسام وبيانات تعريف ومرشحات وإعدادات النمط وخيارات التصدير. قد يتطلب إنشاء كل تقرير من البداية العديد من الخطوات المتكررة.
$report = new Report();
$report->setLayout('professional');
$report->setFont('Arial');
$report->setPageSize('A4');
$report->addSection('Summary');
$report->addSection('Details');
$report->enableCharts();
$report->setExportFormat('pdf');إذا كانت العديد من التقارير تستخدم نفس التكوين الأساسي، فإن تكرار هذه الخطوات في أماكن متعددة ليس أمرًا سهلاً. باستخدام Prototype Pattern، يمكن للتطبيق إنشاء كائن تقرير واحد مُجهز وclone كلما كانت هناك حاجة إلى تقرير مماثل.
Prototype Pattern وCloning
Cloning هي عملية إنشاء نسخة من كائن. في PHP، يمكن أن تكون الكائنات cloned باستخدام الكلمة الأساسية clone.
$copy = clone $original;الكائن المنسوخ هو مثيل جديد، لكنه يحتوي في البداية على نفس قيم خصائص الكائن الأصلي.
ومع ذلك، يجب على المطورين فهم الفرق بين shallow copy وdeep copy. يعد هذا أمرًا مهمًا جدًا عندما تحتوي الكائنات على كائنات أخرى بداخلها.
مثال Prototype الأساسي في PHP
يوضح المثال التالي تطبيق Prototype Pattern البسيط في PHP:
class ReportTemplate
{
public string $title;
public string $layout;
public array $sections = [];
public function __construct(string $title, string $layout)
{
$this->title = $title;
$this->layout = $layout;
}
public function addSection(string $section): void
{
$this->sections[] = $section;
}
}
$defaultReport = new ReportTemplate('Default Report', 'professional');
$defaultReport->addSection('Summary');
$defaultReport->addSection('Details');
$monthlyReport = clone $defaultReport;
$monthlyReport->title = 'Monthly Sales Report';في هذا المثال، يعمل التقرير الافتراضي كـ prototype. يتم إنشاء التقرير الشهري بواسطة cloning التقرير الافتراضي وتغيير العنوان.
استخدام __clone في PHP
يوفر PHP طريقة سحرية خاصة تسمى __clone. يتم استدعاء هذه الطريقة تلقائيًا بعد أن يكون الكائن cloned. يمكن للمطورين استخدامه لإعادة تعيين القيم، أو نسخ الكائنات المتداخلة، أو تحضير الكائن cloned.
class Document
{
public string $title;
public string $documentId;
public function __construct(string $title)
{
$this->title = $title;
$this->documentId = uniqid('doc_', true);
}
public function __clone()
{
$this->documentId = uniqid('doc_', true);
}
}
$original = new Document('Invoice Template');
$copy = clone $original;
echo $original->documentId;
echo $copy->documentId;في هذا المثال، يتلقى مستند cloned معرف مستند جديد. هذا prevent هو المستندات الأصلية وcloned من مشاركة نفس المعرف.
Shallow Copy
يعني shallow copy أنه تم نسخ الكائن نفسه، ولكن لا تزال الكائنات المتداخلة بداخله مشتركة حسب المرجع. يمكن أن يؤدي هذا إلى إنشاء سلوك غير متوقع إذا قام الكائن cloned بتغيير كائن متداخل.
على سبيل المثال، إذا كان كائن التقرير يحتوي على كائن النمط، cloning، فقد يقوم التقرير بنسخ كائن التقرير ولكنه لا يزال يشير إلى نفس كائن النمط. إذا قام تقرير cloned بتغيير النمط، فقد يتأثر التقرير الأصلي أيضًا.
ولهذا السبب يجب على المطورين توخي الحذر عند استخدام كائنات cloning التي تحتوي على كائنات أخرى.
مثال Shallow Copy في PHP
class Style
{
public string $font = 'Arial';
}
class Report
{
public Style $style;
public function __construct()
{
$this->style = new Style();
}
}
$reportA = new Report();
$reportB = clone $reportA;
$reportB->style->font = 'Calibri';
echo $reportA->style->font;في هذا المثال، قد يؤثر تغيير الخط من خلال reportB أيضًا على reportA لأن كلا التقريرين يمكنهما مشاركة نفس كائن النمط بعد clone الضحل.
Deep Copy
يعني deep copy أنه تم نسخ الكائن والكائنات المتداخلة الخاصة به. هذا prevent يمنع الكائن الأصلي وكائن cloned من مشاركة الكائنات الداخلية حسب المرجع.
في PHP، يمكن معالجة deep copy يدويًا داخل أسلوب __clone بواسطة الكائنات المتداخلة cloning.
مثال Deep Copy في PHP
class Style
{
public string $font = 'Arial';
}
class Report
{
public Style $style;
public function __construct()
{
$this->style = new Style();
}
public function __clone()
{
$this->style = clone $this->style;
}
}
$reportA = new Report();
$reportB = clone $reportA;
$reportB->style->font = 'Calibri';
echo $reportA->style->font;في هذا المثال، يتلقى reportB نسخته الخاصة من كائن النمط. لا يؤثر تغيير نمط التقرير cloned على نمط التقرير الأصلي.
Prototype Interface
تحدد بعض التطبيقات prototype interface بأسلوب clone. وهذا يجعل النمط واضحًا ويسمح لـ prototype classes المختلفة باتباع نفس العقد.
interface Prototype
{
public function clone(): Prototype;
}
class InvoiceTemplate implements Prototype
{
public function __construct(
public string $title,
public array $items = []
) {
}
public function clone(): Prototype
{
return clone $this;
}
}يمكن أن تكون هذه البنية مفيدة عندما يعمل التطبيق مع كائنات prototype مختلفة من خلال interface مشترك.
مثال من العالم الحقيقي: قالب الفاتورة
الفواتير هي مثال عملي على Prototype Pattern. قد تشترك العديد من الفواتير في نفس البنية والقواعد الضريبية ونص التذييل والعملة والتخطيط. بدلاً من إنشاء الفاتورة من الصفر في كل مرة، يمكن للنظام clone قالب فاتورة محدد مسبقًا.
class Invoice
{
public string $invoiceNumber;
public string $customerName;
public string $currency = 'USD';
public array $items = [];
public string $footer = 'Thank you for your business.';
public function __construct()
{
$this->invoiceNumber = uniqid('INV-', true);
}
public function __clone()
{
$this->invoiceNumber = uniqid('INV-', true);
$this->items = [];
$this->customerName = '';
}
public function addItem(string $name, float $price): void
{
$this->items[] = [
'name' => $name,
'price' => $price,
];
}
}
$template = new Invoice();
$invoice = clone $template;
$invoice->customerName = 'John Doe';
$invoice->addItem('Web Development Service', 500);في هذا المثال، يوفر قالب الفاتورة قيم العملة والتذييل الافتراضية. تتلقى فاتورة cloned رقم فاتورة جديدًا ويمكن تخصيصها لعميل معين.
مثال من العالم الحقيقي: كائنات اللعبة
يعتبر Prototype Pattern شائعًا في تطوير الألعاب. قد تحتاج اللعبة إلى إنشاء العديد من الأعداء أو الأسلحة أو العناصر أو الشخصيات المتشابهة. بدلاً من تكوين كل كائن من الصفر، يمكن للعبة clone إعداد prototypes.
على سبيل المثال، يمكن للعبة تحديد prototype لعدو أساسي يتمتع بالصحة والسرعة والضرر والمظهر الافتراضي. عندما تحتاج اللعبة إلى المزيد من الأعداء، تقوم clone بتغيير prototype وتغير القيم الصغيرة مثل الموضع أو المستوى.
وهذا يجعل إنشاء الكائنات أكثر كفاءة وأسهل في الإدارة.
مثال من العالم الحقيقي: قوالب المستندات
غالبًا ما تستخدم أنظمة المستندات قوالب العقود والتقارير والفواتير والشهادات والخطابات. يناسب Prototype Pattern هذه الحالة بشكل طبيعي لأنه يمكن تخصيص القالب cloned.
على سبيل المثال، قد يتضمن قالب الشهادة التخطيط الافتراضي والشعار والعنوان ومنطقة التوقيع والنمط. يقوم النظام clone بإنشاء القالب ويقوم بتحديث اسم المستلم والتاريخ ورقم الشهادة.
يؤدي هذا إلى تجنب تكرار تكوين القالب ويضمن الاتساق عبر المستندات التي تم إنشاؤها.
Prototype Pattern مقابل Factory Pattern
يعتبر كل من Prototype Pattern وFactory Pattern من أنماط التصميم إبداعيين، لكنهما يقومان بإنشاء كائنات بشكل مختلف.
يقوم Factory Pattern بإنشاء كائنات عن طريق اختيار class وإنشاء مثيل له. عادةً ما يستخدم constructors وقد يقرر class بناءً على الإدخال أو التكوين.
يقوم Prototype Pattern بإنشاء كائنات عن طريق نسخ كائن موجود. يكون ذلك مفيدًا عندما يكون الكائن في حالة معدة بالفعل ويجب إعادة استخدامها.
باختصار، يسأل Factory "أي class يجب أن أقوم بإنشائه؟" بينما يسأل Prototype "ما هو الكائن الموجود الذي يجب أن أنسخه؟"
Prototype Pattern مقابل Builder Pattern
يقوم Builder Pattern بإنشاء كائنات معقدة خطوة بخطوة. يقوم Prototype Pattern بإنشاء كائنات عن طريق نسخ كائن تم إعداده بالفعل.
استخدم Builder عندما تحتاج عملية الإنشاء إلى أن تكون قابلة للقراءة والتحكم فيها خطوة بخطوة. استخدم Prototype عندما يمكن استخدام كائن جاهز كقالب ونسخه بكفاءة.
على سبيل المثال، يعد EmailMessageBuilder مفيدًا عند إنشاء بريد إلكتروني يحتوي على العديد من الحقول الاختيارية. تعتبر الفاتورة prototype مفيدة عندما تشترك العديد من الفواتير في نفس البنية الافتراضية.
Prototype Pattern مقابل Singleton Pattern
يضمن Singleton Pattern وجود مثيل واحد فقط من class. يقوم Prototype Pattern بإنشاء العديد من المثيلات الجديدة عن طريق نسخ كائن موجود.
هذه الأنماط تحل المشاكل المعاكسة. يحد Singleton من إنشاء الكائنات، بينما يسهل Prototype إنشاء كائنات متعددة متشابهة.
يساعد فهم هذا الاختلاف المطورين على اختيار النمط الصحيح لمشكلة إنشاء الكائن التي يواجهونها.
متى يتم استخدام Prototype Pattern
استخدم Prototype Pattern عندما يكون إنشاء كائن من البداية مكلفًا أو متكررًا أو معقدًا، وعندما يكون نسخ كائن موجود أسهل.
يكون Prototype Pattern مفيدًا عندما:
- هناك حاجة إلى العديد من الكائنات المماثلة.
- تهيئة الكائن مكلفة أو معقدة.
- الكائنات لها العديد من القيم الافتراضية.
- يتم استخدام القوالب أو الإعدادات المسبقة.
- يحتاج التطبيق إلى تجنب منطق الإعداد المتكرر.
- يجب نسخ تكوين كائن Runtime.
- قد لا يكون الكائن المحدد class معروفًا بواسطة كود العميل.
في حالة وجود هذه الشروط، يمكن لـ Prototype Pattern أن يجعل عملية إنشاء الكائن أكثر نظافة وكفاءة.
متى لا تستخدم Prototype Pattern
لا تستخدم Prototype Pattern عندما يكون إنشاء الكائن بسيطًا ومباشرًا. إذا كان الكائن يحتوي على قيم قليلة فقط ولا يوجد إعداد مكلف، فقد يكون استخدام constructor أو factory أكثر وضوحًا.
تجنب Prototype Pattern عندما:
- الكائن سهل الإنشاء.
- يؤدي Cloning إلى حدوث ارتباك أو مراجع مشتركة مخفية.
- يحتوي الكائن على موارد لا ينبغي نسخها مباشرة.
- Deep copying معقد جدًا بحيث لا يمكن إدارته بأمان.
- من شأن builder أو factory أن يعبر عن النية بشكل أكثر وضوحًا.
يجب أن يحل Prototype مشكلة حقيقية. ولا ينبغي إضافته فقط لأن cloning ممكن.
فوائد Prototype Pattern
يوفر Prototype Pattern العديد من الفوائد في تصميم البرامج الموجهة للكائنات.
تشمل الفوائد الرئيسية ما يلي:
- ينشئ كائنات جديدة عن طريق نسخ الكائنات المعدة الموجودة.
- يقلل من منطق التهيئة المتكررة.
- يمكن تحسين الأداء عندما يكون الإعداد مكلفًا.
- يدعم إنشاء الكائنات القائمة على القالب.
- يجعل من السهل إنشاء العديد من الكائنات المماثلة.
- يمكن أن يقلل الاعتماد على concrete classes.
- يعمل بشكل جيد مع المستندات والقوالب والتقارير وكائنات اللعبة.
هذه الفوائد تجعل Prototype Pattern ذا قيمة عندما تشترك الكائنات في حالة بداية مشتركة.
عيوب Prototype Pattern
العيب الرئيسي لـ Prototype Pattern هو أن cloning يمكن أن يكون خادعًا. إذا كانت الكائنات تحتوي على كائنات متداخلة، فقد يتسبب shallow copying في مشاركة الأصلي وclone في المراجع الداخلية.
عيب آخر هو أن كائنات cloning ذات الموارد الخارجية يمكن أن تكون خطيرة. على سبيل المثال، الكائنات التي تحتوي على اتصالات قاعدة البيانات أو مقابض الملفات أو المقابس أو عملاء الخدمة الخارجية يجب ألا تكون دائمًا cloned مباشرة.
يمكن لـ Prototype Pattern أيضًا أن يجعل الكود أقل وضوحًا إذا لم يكن المطورون على دراية بسلوك cloning. من المهم التوثيق الواضح والتنفيذ الدقيق.
Prototype Pattern والكود النظيف
يدعم Prototype Pattern الكود النظيفة عندما يزيل منطق الإعداد المتكرر ويجعل إنشاء الكائن أسهل في الفهم. بدلاً من تكرار العديد من خطوات التكوين، يمكن للتطبيق clone إعداد prototype.
ومع ذلك، يتطلب الكود النظيف الوضوح أيضًا. إذا كان cloning يخفي الكثير من السلوك أو ينشئ حالة مشتركة غير متوقعة، يصبح من الصعب الحفاظ على التصميم.
الاستخدام الجيد لـ Prototype يجب أن يجعل إنشاء الكائنات أسهل، وليس أكثر غموضًا.
Prototype Pattern في Laravel
في تطبيقات Laravel، قد يكون Prototype Pattern مفيدًا عند العمل مع القوالب أو كائنات التكوين أو كائنات التقرير أو DTOs أو إنشاء المستندات أو إعدادات التصدير القابلة لإعادة الاستخدام.
على سبيل المثال، قد تحتفظ وحدة التقارير بكائن تكوين التقرير الافتراضي. عندما يقوم مستخدم request بإعداد تقرير مخصص، يقوم النظام clone بإعداد التكوين الافتراضي وتغيير عوامل التصفية ونطاقات التاريخ وخيارات التصدير.
يوفر Laravel أيضًا مصانع لإنشاء model، والتي تختلف عن Prototype Pattern، ولكن يمكن استخدام كليهما لإنشاء الكائن وفقًا للمشكلة.
Prototype Pattern في Symfony
في مشروعات Symfony، يمكن استخدام Prototype Pattern لكائنات الرسائل المعقدة، أو قوالب command، أو قوالب التقارير، أو كائنات تكوين النموذج، أو قوالب المستندات.
عادةً ما تتم إدارة خدمات Symfony بواسطة dependency injection container، لذلك يجب على المطورين توخي الحذر عند كائنات الخدمة cloning. غالبًا ما يكون Prototype Pattern أكثر ملاءمة لكائنات البيانات والقوالب وكائنات التكوين بدلاً من الخدمات المُدارة بواسطة container ذات التبعيات الخارجية.
الأخطاء الشائعة في Prototype Pattern
أحد الأخطاء الشائعة هو تجاهل الفرق بين shallow copy وdeep copy. يمكن أن يتسبب هذا في قيام كائنات cloned بمشاركة الكائنات المتداخلة بشكل غير متوقع.
خطأ آخر هو كائنات cloning التي تحتوي على موارد خارجية مثل اتصالات قاعدة البيانات أو الملفات المفتوحة. قد لا تتصرف هذه الموارد بشكل صحيح عند نسخها.
الخطأ الثالث هو استخدام Prototype Pattern عندما يكون constructor البسيط أكثر وضوحًا. إذا لم يكن إنشاء الكائن معقدًا، فقد يؤدي cloning إلى إضافة ارتباك غير ضروري.
الخطأ الرابع هو عدم إعادة تعيين القيم الفريدة في كائنات cloned. على سبيل المثال، يجب أن تتلقى الفواتير أو المستندات أو الطلبات cloned عادةً معرفات أو أرقامًا جديدة.
أفضل الممارسات لـ Prototype Pattern
لاستخدام Prototype Pattern بشكل صحيح، يجب على المطورين التحكم في سلوك cloning بعناية.
تتضمن أفضل الممارسات المفيدة ما يلي:
- استخدم Prototype عندما تكون الكائنات ذات إعداد باهظ الثمن أو متكرر.
- استخدم __clone لإعادة تعيين القيم الفريدة مثل المعرفات.
- الكائنات المتداخلة Clone عندما يكون deep copy مطلوبًا.
- تجنب الموارد الخارجية cloning مباشرة.
- قم بتوثيق أجزاء الكائن التي تم نسخها وإعادة تعيينها.
- استخدم prototypes للقوالب والإعدادات المسبقة والتكوينات القابلة لإعادة الاستخدام.
- احتفظ بكائنات cloned مستقلة عندما لا تؤثر التغييرات على الأصل.
- لا تستخدم Prototype عندما يكون constructor أو factory أو builder أكثر وضوحًا.
تساعد هذه الممارسات على إصلاح الأخطاء المتعلقة بـ prevent cloning وتجعل التصميم مفهومًا.
قائمة مرجعية عملية قبل استخدام Prototype Pattern
قبل استخدام Prototype Pattern، يمكن للمطورين طرح هذه الأسئلة:
- هل إنشاء الكائنات مكلف أم متكرر؟
- هل تشترك العديد من الكائنات في نفس الحالة الافتراضية؟
- هل يمكن نسخ كائن مُجهز بأمان؟
- هل يحتوي الكائن على كائنات متداخلة تحتاج إلى deep copying؟
- هل هناك حقول فريدة يجب إعادة تعيينها بعد cloning؟
- هل يحتوي الكائن على موارد لا ينبغي أن تكون cloned؟
- هل سيكون Factory أو Builder أكثر وضوحًا لهذه المشكلة؟
إذا كان الكائن المجهز cloning يجعل الكود أكثر نظافة وأمانًا، فقد يكون Prototype Pattern خيارًا جيدًا.
الاستنتاج
Prototype Pattern هو نمط تصميم إبداعي يقوم بإنشاء كائنات جديدة عن طريق نسخ الكائنات الموجودة. يكون ذلك مفيدًا عندما يكون إنشاء الكائنات معقدًا أو مكلفًا أو متكررًا، وعندما تشترك العديد من الكائنات في نفس البنية الافتراضية.
في PHP، يتم تنفيذ Prototype Pattern بشكل شائع باستخدام الكلمة الأساسية clone والأسلوب السحري __clone. يجب أن يفهم المطورون shallow copy وdeep copy وكيفية إعادة تعيين القيم الفريدة لتجنب السلوك غير المتوقع.
يعد Prototype Pattern مفيدًا للقوالب والتقارير والفواتير وإنشاء المستندات وكائنات اللعبة وكائنات التكوين والإعدادات المسبقة القابلة لإعادة الاستخدام. ومع ذلك، يجب استخدامه بعناية عندما تحتوي الكائنات على كائنات متداخلة أو موارد خارجية. عند تطبيقه بشكل صحيح، يمكن أن يجعل Prototype Pattern عملية إنشاء الكائنات أسرع وأكثر نظافة وأكثر قابلية للصيانة في مشاريع البرامج الموجهة للكائنات.

