الفرق بين المراجعتين لصفحة: «Design Patterns/prototype»
أسامه-دمراني (نقاش | مساهمات) 3.3 تعديل على الصور |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(10 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:نمط النموذج الأولي}}</noinclude> | <noinclude>{{DISPLAYTITLE:نمط النموذج الأولي Prototype}}</noinclude> | ||
نمط | نمط النموذج الأولي (prototype) هو نمط تصميم إنشائي يسمح لك بنسخ الكائنات الموجودة حاليًا دون جعل شيفرتك تعتمد على فئات تلك الكائنات. | ||
== المشكلة == | == المشكلة == | ||
سطر 18: | سطر 18: | ||
== البُنية == | == البُنية == | ||
[[ملف:dpp.structure-indexed.png|تصغير|200x200px|ش.1]] | |||
=== التطبيق الأساسي === | === التطبيق الأساسي === | ||
# تصرح واجهة '''النموذج الأولي (prototype)''' عن أساليب الاستنساخ، وفي أغلب الحالات يكون أسلوب <code>clone</code> وحيد. (انظر ش.1) | |||
# تصرح واجهة '''النموذج الأولي (prototype)''' عن أساليب الاستنساخ، وفي أغلب الحالات يكون أسلوب <code>clone</code> وحيد. | |||
# تستخدم فئة '''النموذج الأولي الحقيقي (Concrete prototype)''' أسلوب الاستنساخ، وإضافة إلى نسخ بيانات الكائن الأصلي إلى المستنسَخ، فقد يتولى هذا الأسلوب أيضًا بعض الحالات الشاذة لعملية الاستنساخ المتعلقة باستنساخ الكائنات المرتبطة ببعضها أو حل الاعتماديات التكرارية (recursive dependencies)، إلخ | # تستخدم فئة '''النموذج الأولي الحقيقي (Concrete prototype)''' أسلوب الاستنساخ، وإضافة إلى نسخ بيانات الكائن الأصلي إلى المستنسَخ، فقد يتولى هذا الأسلوب أيضًا بعض الحالات الشاذة لعملية الاستنساخ المتعلقة باستنساخ الكائنات المرتبطة ببعضها أو حل الاعتماديات التكرارية (recursive dependencies)، إلخ | ||
# يمكن '''للعميل (Client)''' أن ينتج نسخة من أي كائن يتبع واجهة النموذج الأولي. | # يمكن '''للعميل (Client)''' أن ينتج نسخة من أي كائن يتبع واجهة النموذج الأولي. | ||
<br> | |||
<br> | |||
[[ملف:dpp.structure-prototype-cache-indexed.png|تصغير|200x200px|ش.2]] | |||
=== تطبيق سجل النموذج الأولي === | === تطبيق سجل النموذج الأولي === | ||
# يوفر '''سجل النموذج الأولي (Prototype Registry)''' طريقة سهلة للوصول إلى النماذج الأولية المستخدمة بكثرة، فهو يخزن مجموعة من الكائنات المبنية مسبقًا والجاهزة للنسخ. وأسهل تسجيل للنموذ الأولي هو خريطة مزيج <code>name → prototype</code> ، لكن بأي حال، إن كنت تريد معايير بحث أفضل من مجرد اسم بسيط فيمكنك بناء نسخة من السجل أفضل من ذلك وأثبَتْ. (انظر ش.2) | |||
# يوفر '''سجل النموذج الأولي (Prototype Registry)''' طريقة سهلة للوصول إلى النماذج الأولية المستخدمة بكثرة، فهو يخزن مجموعة من الكائنات المبنية مسبقًا والجاهزة للنسخ. وأسهل تسجيل للنموذ الأولي هو خريطة مزيج <code>name → prototype</code> ، لكن بأي حال، إن كنت تريد معايير بحث أفضل من مجرد اسم بسيط فيمكنك بناء نسخة من السجل أفضل من ذلك وأثبَتْ. | <br> | ||
<br> | |||
<br> | |||
== مثال توضيحي == | == مثال توضيحي == | ||
[[ملف:dpp.example.png|تصغير|200x200px|ش.3 استنساخ مجموعة كائنات تنتمي إلى هرمية فئة ما.| | [[ملف:dpp.example.png|تصغير|200x200px|ش.3 استنساخ مجموعة كائنات تنتمي إلى هرمية فئة ما.|يسار]] | ||
يسمح لك نمط النموذج الأولي في هذا المثال بإنتاج نسخ مطابقة من كائنات هندسية دون ربط الشيفرة إلى فئاتها، وتتبع كل فئات <code>shape</code> في هذا المثال نفس الواجهة التي توفر أسلوب استنساخ بدورها، والفئة الفرعية يمكنها استدعاء أسلوب الاستنساخ من فئتها الأم قبل نسخ قيم الحقول الخاصة بها في الكائن الناتج. | يسمح لك نمط النموذج الأولي في هذا المثال بإنتاج نسخ مطابقة من كائنات هندسية دون ربط الشيفرة إلى فئاتها، وتتبع كل فئات <code>shape</code> في هذا المثال نفس الواجهة التي توفر أسلوب استنساخ بدورها، والفئة الفرعية يمكنها استدعاء أسلوب الاستنساخ من فئتها الأم قبل نسخ قيم الحقول الخاصة بها في الكائن الناتج. (انظر ش.3) | ||
<br><br><br><br><br><br> | |||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
سطر 152: | سطر 156: | ||
== العلاقات مع الأنماط الأخرى == | == العلاقات مع الأنماط الأخرى == | ||
* تبدأ العديد من التصميمات مستخدمة أسلوب المصنع بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تتطور إلى أنماط المصنع المجرد والنموذج الأولي | * تبدأ العديد من التصميمات مستخدمة [[Design Patterns/factory method|أسلوب المصنع]] بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تتطور إلى أنماط [[Design Patterns/abstract factory|المصنع المجرد]] والنموذج الأولي و[https://refactoring.guru/design-patterns/builder الباني]، إذ أنها أكثر مرونة لكنها معقدة أكثر في المقابل. | ||
* تُبنى فئات المصنع المجرد عادة على مجموعة من أساليب المصنع، لكنك تستطيع استخدام نمط النموذج الأولي لتركيب الأساليب على تلك الفئات. | * تُبنى فئات المصنع المجرد عادة على مجموعة من أساليب المصنع، لكنك تستطيع استخدام نمط النموذج الأولي لتركيب الأساليب على تلك الفئات. | ||
* التصاميم التي تستخدم نمط المركَّب والمزخرِف قد تستفيد من استخدام نمط النموذج الأولي، إذ يسمح باستخدام بُنى معقدة بدلًا من إعادة إنشائها من الصفر. | * التصاميم التي تستخدم نمط [[Design Patterns/composite|المركَّب]] [[Design Patterns/decorator|والمزخرِف]] قد تستفيد من استخدام نمط النموذج الأولي، إذ يسمح باستخدام بُنى معقدة بدلًا من إعادة إنشائها من الصفر. | ||
* لا يبنى نمط النموذج الأولي على الاكتساب (inheritance) لذا ليس له مساوئه، لكن من الناحية الأخرى فإن النموذج الأولي يتطلب عملية بدء (initialization) معقدة للكائن المستنسخ. في المقابل، أسلوب المصنع مبني على الاكتساب لكنه لا يتطلب خطوة بدء. | * لا يبنى نمط النموذج الأولي على الاكتساب (inheritance) لذا ليس له مساوئه، لكن من الناحية الأخرى فإن النموذج الأولي يتطلب عملية بدء (initialization) معقدة للكائن المستنسخ. في المقابل، أسلوب المصنع مبني على الاكتساب لكنه لا يتطلب خطوة بدء. | ||
* أحيانًا قد يكون نمط النموذج الأول بديلًا بسيطًا لنمط التذكرة (Memento)، حين يكون الكائن، الحالة التي تريد تخزينها في السجل، بسيطة للغاية وليس لديها روابط إلى مصادر خارجية أو أن الروابط يسهل إعادة بناؤها. | * أحيانًا قد يكون نمط النموذج الأول بديلًا بسيطًا لنمط [[التذكرة (Memento)]]، حين يكون الكائن، الحالة التي تريد تخزينها في السجل، بسيطة للغاية وليس لديها روابط إلى مصادر خارجية أو أن الروابط يسهل إعادة بناؤها. | ||
* المصانع المجردة والبانيات والنماذج الأولية يمكن استخدامها جميعها كمفردات. | * المصانع المجردة والبانيات والنماذج الأولية يمكن استخدامها جميعها [[Design Patterns/singleton|كمفردات]]. | ||
== الاستخدام في لغة جافا == | == الاستخدام في لغة جافا == | ||
سطر 433: | سطر 437: | ||
'''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة #C مع واجهة <code>Cloneable</code>. | '''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة #C مع واجهة <code>Cloneable</code>. | ||
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code> | يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code>clone</code> أو <code>copy</code>. | ||
=== مثال: بُنية النمط === | === مثال: بُنية النمط === | ||
سطر 443: | سطر 447: | ||
==== program.cs: مثال هيكلي ==== | ==== program.cs: مثال هيكلي ==== | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
using System; | |||
using System | |||
namespace RefactoringGuru.DesignPatterns.Prototype. | namespace RefactoringGuru.DesignPatterns.Prototype.Conceptual | ||
{ | { | ||
class | public class Person | ||
{ | { | ||
public int Age; | |||
public DateTime BirthDate; | |||
public string Name; | |||
public IdInfo IdInfo; | |||
public Person ShallowCopy() | |||
{ | |||
return (Person) this.MemberwiseClone(); | |||
} | |||
public Person DeepCopy() | |||
{ | { | ||
Person clone = (Person) this.MemberwiseClone(); | |||
clone.IdInfo = new IdInfo(IdInfo.IdNumber); | |||
clone.Name = String.Copy(Name); | |||
return clone; | |||
} | } | ||
} | } | ||
class | public class IdInfo | ||
{ | { | ||
public | public int IdNumber; | ||
public IdInfo(int idNumber) | |||
{ | { | ||
this.IdNumber = idNumber; | |||
} | } | ||
} | } | ||
class Program | |||
{ | { | ||
static void Main(string[] args) | |||
{ | { | ||
Person p1 = new Person(); | |||
p1.Age = 42; | |||
p1.BirthDate = Convert.ToDateTime("1977-01-01"); | |||
p1.Name = "Jack Daniels"; | |||
p1.IdInfo = new IdInfo(666); | |||
// P2 وأسندها إلى P1 خذ نسخة سطحية من . | |||
Person p2 = p1.ShallowCopy(); | |||
// P3 وأسندها إلى P1 خذ نسخة عميقة من . | |||
Person p3 = p1.DeepCopy(); | |||
// P3 و P2 و P1 اعرض قيم. | |||
Console.WriteLine("Original values of p1, p2, p3:"); | |||
Console.WriteLine(" p1 instance values: "); | |||
DisplayValues(p1); | |||
Console.WriteLine(" p2 instance values:"); | |||
DisplayValues(p2); | |||
Console.WriteLine(" p3 instance values:"); | |||
DisplayValues(p3); | |||
// P3 و P2 و P1واعرض قيم P1 غيّر قيمة الخصائص لـ | |||
p1.Age = 32; | |||
p1.BirthDate = Convert.ToDateTime("1900-01-01"); | |||
p1.Name = "Frank"; | |||
p1.IdInfo.IdNumber = 7878; | |||
Console.WriteLine("\nValues of p1, p2 and p3 after changes to p1:"); | |||
Console.WriteLine(" p1 instance values: "); | |||
DisplayValues(p1); | |||
Console.WriteLine(" p2 instance values (reference values have changed):"); | |||
DisplayValues(p2); | |||
Console.WriteLine(" p3 instance values (everything was kept the same):"); | |||
DisplayValues(p3); | |||
} | } | ||
public | public static void DisplayValues(Person p) | ||
{ | { | ||
Console.WriteLine(" Name: {0:s}, Age: {1:d}, BirthDate: {2:MM/dd/yy}", | |||
p.Name, p.Age, p.BirthDate); | |||
Console.WriteLine(" ID#: {0:d}", p.IdInfo.IdNumber); | |||
} | } | ||
} | } | ||
سطر 547: | سطر 532: | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
Original values of p1, p2, p3: | |||
p1 instance values: | |||
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77 | |||
ID#: 666 | |||
p2 instance values: | |||
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77 | |||
ID#: 666 | |||
p3 instance values: | |||
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77 | |||
ID#: 666 | |||
Values of p1, p2 and p3 after changes to p1: | |||
p1 instance values: | |||
Name: Frank, Age: 32, BirthDate: 01/01/00 | |||
ID#: 7878 | |||
p2 instance values (reference values have changed): | |||
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77 | |||
ID#: 7878 | |||
p3 instance values (everything was kept the same): | |||
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77 | |||
ID#: 666 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 558: | سطر 560: | ||
'''الانتشار: ★ ★ ☆''' | '''الانتشار: ★ ★ ☆''' | ||
أمثلة الاستخدام: نمط النموذج الأولي متاح في لغة PHP مباشرة، يمكنك استخدام كلمة <code>clone</code> المفتاحية لإنشاء نسخة طبق الأصل من كائن ما، أما لإضافة دعم الاستنساخ إلى فئة ما فستحتاج إلى استخدام أسلوب <code>clone__</code>. | '''أمثلة الاستخدام:''' نمط النموذج الأولي متاح في لغة [[PHP]] مباشرة، يمكنك استخدام كلمة <code>clone</code> المفتاحية لإنشاء نسخة طبق الأصل من كائن ما، أما لإضافة دعم الاستنساخ إلى فئة ما فستحتاج إلى استخدام أسلوب <code>clone__</code>. | ||
=== مثال: بنية النمط === | === مثال: بنية النمط === | ||
سطر 573: | سطر 575: | ||
/** | /** | ||
* فئة المثال | * فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع. | ||
*/ | */ | ||
class Prototype | class Prototype | ||
سطر 653: | سطر 655: | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Primitive field values have been carried over to a clone. Yay! | Primitive field values have been carried over to a clone. Yay! | ||
Simple component has been cloned. Yay! | Simple component has been cloned. Yay! | ||
سطر 786: | سطر 788: | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Dump of the clone. Note that the author is now referencing two objects. | Dump of the clone. Note that the author is now referencing two objects. | ||
سطر 837: | سطر 839: | ||
) | ) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== الاستخدام في لغة بايثون == | |||
'''المستوى:''' ★ ☆ ☆ | |||
'''الانتشار: ★ ★ ☆''' | |||
'''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة بايثون مع واجهة <code>Cloneable</code>. | |||
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code>clone</code> أو <code>copy</code>. | |||
=== مثال: بُنية النمط === | |||
يوضح هذا المثال بنية نمط '''النموذج الأولي'''، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== main.py: مثال تصوري ==== | |||
<syntaxhighlight lang="python"> | |||
from __future__ import annotations | |||
from datetime import datetime | |||
from copy import deepcopy | |||
from typing import Any | |||
class Prototype: | |||
""" | |||
فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع. | |||
""" | |||
def __init__(self) -> None: | |||
self._primitive = None | |||
self._component = None | |||
self._circular_reference = None | |||
@property | |||
def primitive(self) -> Any: | |||
return self._primitive | |||
@primitive.setter | |||
def primitive(self, value: Any) -> None: | |||
self._primitive = value | |||
@property | |||
def component(self) -> object: | |||
return self._component | |||
@component.setter | |||
def component(self, value: object) -> None: | |||
self._component = value | |||
@property | |||
def circular_reference(self) -> ComponentWithBackReference: | |||
return self._circular_reference | |||
@circular_reference.setter | |||
def circular_reference(self, value: ComponentWithBackReference) -> None: | |||
self._circular_reference = value | |||
def clone(self) -> Prototype: | |||
self.component = deepcopy(self.component) | |||
# "backreference" تحتاج إلى معاملة خاصة في حالة استنساخ كائن له مرجعية | |||
# مع كائن آخر، فبعد تمام الاستنساخ يجب أن يشير (nested) خلفية ومتداخل | |||
# الكائن المتداخل إلى الكائن المستنسَخ بدلًا من الكائن الأصلي. | |||
self.circular_reference = deepcopy(self.circular_reference) | |||
self.circular_reference.prototype = self | |||
return deepcopy(self) | |||
class ComponentWithBackReference: | |||
def __init__(self, prototype: Prototype): | |||
self._prototype = prototype | |||
@property | |||
def prototype(self) -> Prototype: | |||
return self._prototype | |||
@prototype.setter | |||
def prototype(self, value: Prototype) -> None: | |||
self._prototype = value | |||
if __name__ == "__main__": | |||
# شيفرة العميل. | |||
p1 = Prototype() | |||
p1.primitive = 245 | |||
p1.component = datetime.now() | |||
p1.circular_reference = ComponentWithBackReference(p1) | |||
p2 = p1.clone() | |||
if p1.primitive is p2.primitive: | |||
print("Primitive field values have been carried over to a clone. Yay!") | |||
else: | |||
print("Primitive field values have not been copied. Booo!") | |||
if p1.component is p2.component: | |||
print("Simple component has not been cloned. Booo!") | |||
else: | |||
print("Simple component has been cloned. Yay!") | |||
if p1.circular_reference is p2.circular_reference: | |||
print("Component with back reference has not been cloned. Booo!") | |||
else: | |||
print("Component with back reference has been cloned. Yay!") | |||
if p1.circular_reference.prototype is p2.circular_reference.prototype: | |||
print("Component with back reference is linked to original object. Booo!", end="") | |||
else: | |||
print("Component with back reference is linked to the clone. Yay!", end="") | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Primitive field values have been carried over to a clone. Yay! | |||
Simple component has been cloned. Yay! | |||
Component with back reference has been cloned. Yay! | |||
Component with back reference is linked to the clone. Yay! | |||
</syntaxhighlight> | |||
== الاستخدام في لغة روبي == | |||
'''المستوى:''' ★ ☆ ☆ | |||
'''الانتشار: ★ ★ ☆''' | |||
'''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة روبي مع واجهة <code>Cloneable</code>. | |||
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code>clone</code> أو <code>copy</code>. | |||
=== مثال: بُنية النمط === | |||
يوضح هذا المثال بنية نمط '''النموذج الأولي'''، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== main.rb: مثال تصوري ==== | |||
<syntaxhighlight lang="ruby"> | |||
# فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع. | |||
class Prototype | |||
attr_accessor :primitive, :component, :circular_reference | |||
def initialize | |||
@primitive = nil | |||
@component = nil | |||
@circular_reference = nil | |||
end | |||
# @return [Prototype] | |||
def clone | |||
@component = deep_copy(@component) | |||
# "backreference" تحتاج إلى معاملة خاصة في حالة استنساخ كائن له مرجعية | |||
# مع كائن آخر، فبعد تمام الاستنساخ يجب أن يشير (nested) خلفية ومتداخل | |||
# الكائن المتداخل إلى الكائن المستنسَخ بدلًا من الكائن الأصلي. | |||
@circular_reference = deep_copy(@circular_reference) | |||
@circular_reference.prototype = self | |||
deep_copy(self) | |||
end | |||
# هو الأسلوب المعتاد لصنع نسخة عميقة، لكنه بطيء وغير كفء deep_copy | |||
# خاصة عند العمل مع التطبيقات الحقيقية gem لهذا، استخدم. | |||
private def deep_copy(object) | |||
Marshal.load(Marshal.dump(object)) | |||
end | |||
end | |||
class ComponentWithBackReference | |||
attr_accessor :prototype | |||
# @param [Prototype] prototype | |||
def initialize(prototype) | |||
@prototype = prototype | |||
end | |||
end | |||
# شيفرة العميل. | |||
p1 = Prototype.new | |||
p1.primitive = 245 | |||
p1.component = Time.now | |||
p1.circular_reference = ComponentWithBackReference.new(p1) | |||
p2 = p1.clone | |||
if p1.primitive == p2.primitive | |||
puts 'Primitive field values have been carried over to a clone. Yay!' | |||
else | |||
puts 'Primitive field values have not been copied. Booo!' | |||
end | |||
if p1.component.equal?(p2.component) | |||
puts 'Simple component has not been cloned. Booo!' | |||
else | |||
puts 'Simple component has been cloned. Yay!' | |||
end | |||
if p1.circular_reference.equal?(p2.circular_reference) | |||
puts 'Component with back reference has not been cloned. Booo!' | |||
else | |||
puts 'Component with back reference has been cloned. Yay!' | |||
end | |||
if p1.circular_reference.prototype.equal?(p2.circular_reference.prototype) | |||
print 'Component with back reference is linked to original object. Booo!' | |||
else | |||
print 'Component with back reference is linked to the clone. Yay!' | |||
end | |||
</syntaxhighlight> | |||
==== output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Primitive field values have been carried over to a clone. Yay! | |||
Simple component has been cloned. Yay! | |||
Component with back reference has been cloned. Yay! | |||
Component with back reference is linked to the clone. Yay! | |||
</syntaxhighlight> | |||
== الاستخدام في لغة Swift == | |||
'''المستوى:''' ★ ☆ ☆ | |||
'''الانتشار: ★ ★ ☆''' | |||
'''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة Swift مع واجهة <code>Cloneable</code>. | |||
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code>clone</code> أو <code>copy</code>. | |||
=== مثال: بُنية النمط === | |||
يوضح هذا المثال بنية نمط '''النموذج الأولي'''، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift. | |||
==== Example.swift: مثال تصوري ==== | |||
<syntaxhighlight lang="swift"> | |||
import XCTest | |||
/// تدعم الاستنساخ إذ هو مضمن فيها، ولكي تضيف ذلك الدعم إلى فئتك Swift لغة | |||
/// copy في تلك الفئة وتوفير الاستخدام لأسلوب NSCopying فإنك تحتاج إلى استخدام بروتوكول. | |||
class BaseClass: NSCopying, Equatable { | |||
private var intValue = 1 | |||
private var stringValue = "Value" | |||
required init(intValue: Int = 1, stringValue: String = "Value") { | |||
self.intValue = intValue | |||
self.stringValue = stringValue | |||
} | |||
/// MARK: - NSCopying | |||
func copy(with zone: NSZone? = nil) -> Any { | |||
let prototype = type(of: self).init() | |||
prototype.intValue = intValue | |||
prototype.stringValue = stringValue | |||
print("Values defined in BaseClass have been cloned!") | |||
return prototype | |||
} | |||
/// MARK: - Equatable | |||
static func == (lhs: BaseClass, rhs: BaseClass) -> Bool { | |||
return lhs.intValue == rhs.intValue && lhs.stringValue == rhs.stringValue | |||
} | |||
} | |||
/// لتنسخ بياناتها داخل copy تستطيع الفئات الفرعية أن تتجاوز أسلوب | |||
/// الكائن الناتج، لكن يجب أن تستدعي الأسلوب الأساسي أولًا. | |||
class SubClass: BaseClass { | |||
private var boolValue = true | |||
func copy() -> Any { | |||
return copy(with: nil) | |||
} | |||
override func copy(with zone: NSZone?) -> Any { | |||
guard let prototype = super.copy(with: zone) as? SubClass else { | |||
return SubClass() // oops | |||
} | |||
prototype.boolValue = boolValue | |||
print("Values defined in SubClass have been cloned!") | |||
return prototype | |||
} | |||
} | |||
/// شيفرة العميل. | |||
class Client { | |||
// ... | |||
static func someClientCode() { | |||
let original = SubClass(intValue: 2, stringValue: "Value2") | |||
guard let copy = original.copy() as? SubClass else { | |||
XCTAssert(false) | |||
return | |||
} | |||
/// للمزيد من التفاصيل Equatable انظر استخدام بروتوكول. | |||
XCTAssert(copy == original) | |||
print("The original object is equal to the copied object!") | |||
} | |||
// ... | |||
} | |||
/// لنرى الآن كيف سيعمل كل ذلك. | |||
class PrototypeConceptual: XCTestCase { | |||
func testPrototype_NSCopying() { | |||
Client.someClientCode(); | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Values defined in BaseClass have been cloned! | |||
Values defined in SubClass have been cloned! | |||
The original object is equal to the copied object! | |||
</syntaxhighlight> | |||
=== مثال واقعي === | |||
==== Example.swift: مثال واقعي ==== | |||
<syntaxhighlight lang="swift"> | |||
import XCTest | |||
class PrototypeRealWorld: XCTestCase { | |||
func testPrototypeRealWorld() { | |||
let author = Author(id: 10, username: "Ivan_83") | |||
let page = Page(title: "My First Page", contents: "Hello world!", author: author) | |||
page.add(comment: Comment(message: "Keep it up!")) | |||
/// يعيد أي شيء NSCopying بما أن. | |||
guard let anotherPage = page.copy() as? Page else { | |||
XCTFail("Page was not copied") | |||
return | |||
} | |||
/// يجب أن تكون التعليقات فارغة بما أن الصفحة جديدة. | |||
XCTAssert(anotherPage.comments.isEmpty) | |||
/// لاحظ أن المؤلف يشير الآن إلى كائنين. | |||
XCTAssert(author.pagesCount == 2) | |||
print("Original title: " + page.title) | |||
print("Copied title: " + anotherPage.title) | |||
print("Count of pages: " + String(author.pagesCount)) | |||
} | |||
} | |||
private class Author { | |||
private var id: Int | |||
private var username: String | |||
private var pages = [Page]() | |||
init(id: Int, username: String) { | |||
self.id = id | |||
self.username = username | |||
} | |||
func add(page: Page) { | |||
pages.append(page) | |||
} | |||
var pagesCount: Int { | |||
return pages.count | |||
} | |||
} | |||
private class Page: NSCopying { | |||
private(set) var title: String | |||
private(set) var contents: String | |||
private weak var author: Author? | |||
private(set) var comments = [Comment]() | |||
init(title: String, contents: String, author: Author?) { | |||
self.title = title | |||
self.contents = contents | |||
self.author = author | |||
author?.add(page: self) | |||
} | |||
func add(comment: Comment) { | |||
comments.append(comment) | |||
} | |||
/// MARK: - NSCopying | |||
func copy(with zone: NSZone? = nil) -> Any { | |||
return Page(title: "Copy of '" + title + "'", contents: contents, author: author) | |||
} | |||
} | |||
private struct Comment { | |||
let date = Date() | |||
let message: String | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Original title: My First Page | |||
Copied title: Copy of 'My First Page' | |||
Count of pages: 2 | |||
</syntaxhighlight> | |||
== الاستخدام في لغة TypeScript == | |||
'''المستوى:''' ★ ☆ ☆ | |||
'''الانتشار: ★ ★ ☆''' | |||
'''أمثلة الاستخدام''': نمط النموذج الأولي متاح افتراضيًا في لغة TypeScript مع واجهة <code>Cloneable</code>. | |||
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل <code>clone</code> أو <code>copy</code>. | |||
=== مثال: بُنية النمط === | |||
يوضح هذا المثال بنية نمط '''النموذج الأولي'''، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== index.ts: مثال تصوري ==== | |||
<syntaxhighlight lang="text"> | |||
/** | |||
* فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع. | |||
*/ | |||
class Prototype { | |||
public primitive: any; | |||
public component: object; | |||
public circularReference: ComponentWithBackReference; | |||
public clone(): this { | |||
const clone = Object.create(this); | |||
clone.component = Object.create(this.component); | |||
// يحتاج استنساخ كائن به كائن متداخل مع مرجع خلفي إلى معاملة خاصة، | |||
// ذلك أنه بعد انتهاء الاستنساخ فإن الكائن المتداخل يجب أن يشير إلى | |||
// الكائن المستنسَخ، بدلًا من الكائن الأصلي. | |||
// مفيدًا ها هنا Spread قد يكون معامِل. | |||
clone.circularReference = { | |||
...this.circularReference, | |||
prototype: { ...this }, | |||
}; | |||
return clone; | |||
} | |||
} | |||
class ComponentWithBackReference { | |||
public prototype; | |||
constructor(prototype: Prototype) { | |||
this.prototype = prototype; | |||
} | |||
} | |||
/** | |||
* شيفرة العميل. | |||
*/ | |||
function clientCode() { | |||
const p1 = new Prototype(); | |||
p1.primitive = 245; | |||
p1.component = new Date(); | |||
p1.circularReference = new ComponentWithBackReference(p1); | |||
const p2 = p1.clone(); | |||
if (p1.primitive === p2.primitive) { | |||
console.log('Primitive field values have been carried over to a clone. Yay!'); | |||
} else { | |||
console.log('Primitive field values have not been copied. Booo!'); | |||
} | |||
if (p1.component === p2.component) { | |||
console.log('Simple component has not been cloned. Booo!'); | |||
} else { | |||
console.log('Simple component has been cloned. Yay!'); | |||
} | |||
if (p1.circularReference === p2.circularReference) { | |||
console.log('Component with back reference has not been cloned. Booo!'); | |||
} else { | |||
console.log('Component with back reference has been cloned. Yay!'); | |||
} | |||
if (p1.circularReference.prototype === p2.circularReference.prototype) { | |||
console.log('Component with back reference is linked to original object. Booo!'); | |||
} else { | |||
console.log('Component with back reference is linked to the clone. Yay!'); | |||
} | |||
} | |||
clientCode(); | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Primitive field values have been carried over to a clone. Yay! | |||
Simple component has been cloned. Yay! | |||
Component with back reference has been cloned. Yay! | |||
Component with back reference is linked to the clone. Yay! | |||
</syntaxhighlight> | |||
== انظر أيضًا == | |||
* [[Design Patterns/decorator|نمط المزخرف Decorator]]. | |||
* [[Design Patterns/composite|نمط المركَّب Composite]]. | |||
* [[Design Patterns/singleton|نمط الورقة المفردة Singleton]]. | |||
* [[Design Patterns/abstract factory|نمط المصنع المجرَّد Abstract Factory]]. | |||
== مصادر == | |||
* [https://refactoring.guru/design-patterns/prototype توثيق نمط النموذج الأولي في موقع reafactoring.guru]. | |||
[[تصنيف:Design Patterns]] | |||
[[تصنيف:Prototype Design Pattern]] | |||
[[تصنيف:Creational Patterns]] |
المراجعة الحالية بتاريخ 10:47، 7 أكتوبر 2022
نمط النموذج الأولي (prototype) هو نمط تصميم إنشائي يسمح لك بنسخ الكائنات الموجودة حاليًا دون جعل شيفرتك تعتمد على فئات تلك الكائنات.
المشكلة
لنقل أن لديك كائنًا تريد إنشاء نسخة طبق الأصل منه، كيف ستفعل ذلك؟ ستنشئ أولًا كائنًا جديدًا من نفس الفئة ثم ستنسخ كل القيم الموجودة في الكائن الأصلي إلى الكائن الجديد. لكن ليست كل الكائنات يمكن نسخها بتلك الطريقة إذ أن بعض حقول الكائن قد تكون خاصة وغير مرئية من خارج الكائن نفسه.
لديك مشكلة ثانية مع هذا المنظور المباشر، فلأن عليك معرفة فئة الكائن لإنشاء نسخة منه، فإن شيفرتك ستعتمد على تلك الفئة، وإن لم تقلقك تلك الاعتماديات الإضافية فهناك مشكلة أخرى، فإنك أحيانًا قد لا تعرف سوى الواجهة التي يتبعها ذلك الكائن ولا تعرف فئته الحقيقية، في حين أنك قد يكون لديك معامِل مثلًا يقبل أي كائنات تتبع واجهة ما.
الحل
يفوض نمط النموذج الأولي عملية الاستنساخ إلى الكائنات الحقيقية التي تُستنسخ، ويصرّح عن واجهة مشتركة لكل الكائنات التي تدعم الاستنساخ، تسمح لك تلك الواجهة باستنساخ كائن ما دون ربط شيفرتك إلى فئة ذلك الكائن، وتحتوي تلك الواجهة عادة على أسلوب clone
وحيد.
ويتشابه استخدام أسلوب clone
إلى حد كبير في كل الفئات، فهو ينشئ كائنًا من الفئة الحالية وينقل كل قيم الحقول من الكائن القديم إلى الجديد، بل يمكنك أيضًا نسخ الحقول الخاصة (private fields) بسبب أن أغلب لغات البرمجة تسمح للكائنات بالوصول إلى الحقول الخاصة للكائنات الأخرى التي تنتمي إلى نفس الفئة. ويسمى الكائن الذي يدعم الاستنساخ بالنموذج الأولي (prototype)، وستجد أن عملية الاستنساخ مفيدة عندما تحتوي الكائنات التي لديك على عشرات الحقول ومئات الإعدادات المحتملة، إذ سيكون الاستنساخ حينها بديلًا للفئات الفرعية.
وإليك كيف تستنسخ الكائنات حين تحتاجها: تنشئ مجموعة كائنات وتهيئ إعداداتها كما تشاء، ثم حين تحتاج كائنًا شبيهًا بأحد تلك الكائنات التي أنشأتها فإنك تستنسخ نموذجًا أوليًا (prototype) بدلًا من إنشاء كائن جديد من الصفر.
مثال حي
تُستخدم النماذج الأولية في المجالات الصناعية لإجراء اختبارات عديدة قبل البدء في الإنتاج بكميات كبيرة، لكن لا تساهم في أي منتج حقيقي، وإنما تقوم بدور مستترن وعليه فإن النماذج الأولية الصناعية لا تنسخ أنفسها حقًا وإنما تقوم مقال المراحل الأولية التجريبية للمنتج الفعلي، ولذا فإن أقرب مثال يوضح فكرة نمط النموذج الأولي سيكون انقسام الخلايا غير المباشر في الكائنات الحية، فبعده يكون لدينا زوجًا من خليتين متطابقتين تمامًا، وتكون الخلية الأصلية هنا بمثابة النموذج الأولي وتؤدي دورًا وظيفيًا يماثل دور الخلية المستنسَخة تمامًا في الكائن الحي.
البُنية
التطبيق الأساسي
- تصرح واجهة النموذج الأولي (prototype) عن أساليب الاستنساخ، وفي أغلب الحالات يكون أسلوب
clone
وحيد. (انظر ش.1) - تستخدم فئة النموذج الأولي الحقيقي (Concrete prototype) أسلوب الاستنساخ، وإضافة إلى نسخ بيانات الكائن الأصلي إلى المستنسَخ، فقد يتولى هذا الأسلوب أيضًا بعض الحالات الشاذة لعملية الاستنساخ المتعلقة باستنساخ الكائنات المرتبطة ببعضها أو حل الاعتماديات التكرارية (recursive dependencies)، إلخ
- يمكن للعميل (Client) أن ينتج نسخة من أي كائن يتبع واجهة النموذج الأولي.
تطبيق سجل النموذج الأولي
- يوفر سجل النموذج الأولي (Prototype Registry) طريقة سهلة للوصول إلى النماذج الأولية المستخدمة بكثرة، فهو يخزن مجموعة من الكائنات المبنية مسبقًا والجاهزة للنسخ. وأسهل تسجيل للنموذ الأولي هو خريطة مزيج
name → prototype
، لكن بأي حال، إن كنت تريد معايير بحث أفضل من مجرد اسم بسيط فيمكنك بناء نسخة من السجل أفضل من ذلك وأثبَتْ. (انظر ش.2)
مثال توضيحي
يسمح لك نمط النموذج الأولي في هذا المثال بإنتاج نسخ مطابقة من كائنات هندسية دون ربط الشيفرة إلى فئاتها، وتتبع كل فئات shape
في هذا المثال نفس الواجهة التي توفر أسلوب استنساخ بدورها، والفئة الفرعية يمكنها استدعاء أسلوب الاستنساخ من فئتها الأم قبل نسخ قيم الحقول الخاصة بها في الكائن الناتج. (انظر ش.3)
// النموذج الأولي الأساسي.
abstract class Shape is
field X: int
field Y: int
field color: string
// مؤسس عادي.
constructor Shape() is
// ...
// مؤسس النموذج الأولي. يُبدأ كائن جديد بقيم من الكائن الموجود فعلًا.
constructor Shape(source: Shape) is
this()
this.X = source.X
this.Y = source.Y
this.color = source.color
// الفرعية shape تعيد عملية الاستنساخ إحدى فئات.
abstract method clone():Shape
// النموذج الأولي الحقيقي. تنشئ عملية الاستنساخ كائنًا جديدًا وتمرره إلى
// المؤسس. ويظل للمؤسس مرجعٌ إلى نسخة حديثة حتي ينتهي، لذا لا يكون لأي أحد
// وصول إلى نسخة غير مكتملة البناء من أجل الحفاظ على ثبات النتيجة.
class Rectangle extends Shape is
field width: int
field height: int
constructor Rectangle(source: Rectangle) is
// استدعاء المؤسس الأساسي مطلوب لنسخ الحقول الخاصة المعرَّفة في
// الفئة الأم.
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)
// في مكان ما داخل شيفرة العميل
class Application is
field shapes: array of Shape
constructor Application() is
Circle circle = new Circle()
circle.width = 10
circle.height = 10
circle.radius = 20
shapes.add(circle)
Circle anotherCircle = circle.clone()
shapes.add(anotherCircle)
// على نسخة طبق الأصل من 'anotherCircle' يحتوي متغير
// 'circle' كائن.
Rectangle rectangle = new Rectangle()
rectangle.width = 10
rectangle.height = 20
shapes.add(rectangle)
method businessLogic() is
// تظهر أفضلية النموذج الأولي هنا إذ يسمح لك بإنتاج نسخة من الكائن
// دون معرفة أي شيء عن نوعه.
Array shapesCopy = new Array of Shapes.
// فمثلًا، لا نعرف عناصر مصفوفة الأشكال بالضبط، فكل ما نعرفه أنها أشكال
// 'clone' كلها. لكن بفضل تعدد الأشكال فإننا حين نستدعي أسلوب
// على شكل ما، فإن البرنامج يفحص فئته الحقيقية وينفِّذ أسلوب الاستنساخ
// المناسب والمعرَّف في تلك الفئة. هذا هو سبب حصولنا على نسخ صحيحة
// ومناسبة بدلًا من مجموعة كائنات أشكال بسيطة.
foreach (s in shapes) do
shapesCopy.add(s.clone())
// على نسخ طبق الأصل من `shapesCopy` تحتوي مصفوفة
// `shape` عناصر مصفوفة.
قابلية التطبيق
استخدم نمط النموذج الأولي حين تريد لشيفرتك ألا تعتمد على الفئات الحقيقية للكائنات التي تريد نسخها.
يحدث هذا كثيرًا حين تعمل شيفرتك مع كائنات ممررة إليك من شيفرة خارجية عبر واجهة ما، ولا تعرف الفئات الحقيقية لتلك الكائناتن ولا يمكنك الاعتماد عليها حتى لو أردت. فيوفر نمط النموذج الأولي ها هنا لشيفرة العميل واجهة عامة للعمل مع كل الكائنات التي تدعم الاستنساخ، وتلك الواجهة تجعل شيفرة العميل مستقلة عن الفئات الحقيقية للكائنات التي تستنسخها.
استخدم النمط حين تريد تقليل عدد الفئات الفرعية التي لا تختلف إلا في طريقة بدء كائناتها، فقد يكون أحدهم قد أنشأ تلك الفئات الفرعية ليتمكن من إنشاء كائنات بإعدادات محددة.
يسمح لك نمط النموذج الأولي باستخدام مجموعة كائنات مسبقة البناء ومُعدَّة بطرق مختلفة، يسمح لك باستخدامها كنماذج أولية. وبدلًا من تمثيل فئة فرعية تطابق بعض الإعدادات، فإن العميل يمكنه البحث ببساطة عن النموذج الأولي المناسب ويستنسخه.
كيفية الاستخدام
- أنشئ واجهة النموذج الأولي وصرّح عن أسلوب
clone
فيها، أو أضف الأسلوب إلى كل الفئات في الهرمية الحالية للفئات إن كانت لديك. - يجب أن تعرِّف فئة النموذج الأولي المنشئ البديل الذي يقبل كائنًا من تلك الفئة كوسيط، ويجب أن ينسخ المنشئ فيم كل الحقول المعرَّفة في تلك الفئة من الكائن المُمرَّر إلى النسخة المنشأة حديثًا. وإن كنت تغير فئة فرعية فيجب أن تستدعي المنشئ الأساسي (parent constructor) ليسمح للفئة الأساسية (superclass) بتولي عملية استنساخ حقولها الخاصة. أما إن كانت اللغة البرمجية التي تستخدمها لا تدعم التحميل الزائد (overloading)، فيمكنك أن تعرِّف أسلوبًا خاصًا لنسخ بيانات الكائن، ويمكنك استخدام المنشئ (constructor) كمكان مناسب لفعل ذلك بسبب أنه يسلم الكائن الناتج مباشرة بعد استدعاء معامِل
new
. - يتكون أسلوب الاستنساخ عادة من سطر واحد: تشغيل معامِل
new
مع النسخة النموذجية من المنشئ، لاحظ أنه يجب على كل فئة أن تتخطى أسلوب الاستنساخ وتستخدم اسم فئتها مع معاملnew
، وإلا فإن أسلوب الاستنساخ قد ينتج كائنًا من الفئة الأم. - أنشئ سجل نموذج أولي مركزي لتخزين فهرس بالنماذج الأولية التي تستخدم بكثرة.
يمكنك استخدام السجل كفئة مصنع جديد أو وضعه في فئة النموذج الأولي الأساسي مع أسلوب ثابت (static method) لجلب النموذج الأولي، يجب أن يبحث هذا الأسلوب عن النموذج الأولي بناءً على معيار البحث الذي تمرره شيفرة العميل إلى الأسلوب، وقد يكون معيار البحث نصًا بسيطًا أو مجموعة معامِلات بحث معقدة. وعند العثور على النموذج الأولي المناسب فإن السجل يستنسخه ويعيد النسخة إلى العميل.
وأخيرًا، استبدل الاستدعاءات المباشرة إلى منشئات الفئات الفرعيةباستدعاءات إلى أسلوب المصنع لسجل النموذج الأولي.
المزايا والعيوب
المزايا
- يمكنك استنساخ الكائنات دون ربطها بفئاتها الحقيقية.
- يمكنك التخلص من شيفرة البدء المتكررة لصالح نسخ نماذج أولية مبنية مسبقًا.
- يمكنك إنتاج كائنات معقدة بشكل أيسر.
- تحصل على بديل للاكتساب (Inheritance) عند التعامل مع تجهيزات الإعدادات المسبقة للكائنات المعقدة.
العيوب
- قد يكون من الصعب استنساخ الكائنات المعقدة التي لديها مراجع حِلَقية (circular references).
العلاقات مع الأنماط الأخرى
- تبدأ العديد من التصميمات مستخدمة أسلوب المصنع بما أنه أقل تعقيدًا وأكثر قابلية للتخصيص عبر الفئات الفرعية، ثم تتطور إلى أنماط المصنع المجرد والنموذج الأولي والباني، إذ أنها أكثر مرونة لكنها معقدة أكثر في المقابل.
- تُبنى فئات المصنع المجرد عادة على مجموعة من أساليب المصنع، لكنك تستطيع استخدام نمط النموذج الأولي لتركيب الأساليب على تلك الفئات.
- التصاميم التي تستخدم نمط المركَّب والمزخرِف قد تستفيد من استخدام نمط النموذج الأولي، إذ يسمح باستخدام بُنى معقدة بدلًا من إعادة إنشائها من الصفر.
- لا يبنى نمط النموذج الأولي على الاكتساب (inheritance) لذا ليس له مساوئه، لكن من الناحية الأخرى فإن النموذج الأولي يتطلب عملية بدء (initialization) معقدة للكائن المستنسخ. في المقابل، أسلوب المصنع مبني على الاكتساب لكنه لا يتطلب خطوة بدء.
- أحيانًا قد يكون نمط النموذج الأول بديلًا بسيطًا لنمط التذكرة (Memento)، حين يكون الكائن، الحالة التي تريد تخزينها في السجل، بسيطة للغاية وليس لديها روابط إلى مصادر خارجية أو أن الروابط يسهل إعادة بناؤها.
- المصانع المجردة والبانيات والنماذج الأولية يمكن استخدامها جميعها كمفردات.
الاستخدام في لغة جافا
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في جافا، مع واجهة Cloneable
، وأي فئة تستطيع استخدام تلك الواجهة لتصير قابلة للاستنساخ.
java.lang.Object#clone(): يجب أن تستخدم الفئة واجهة java.lang.Cloneable.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل ()clone
أو ()copy
.
مثال: نسخ الأشكال المرئية
يوضح المثال التالي كيفية استخدام نمط النموذج الأولي بدون واجهة Cloneable
القياسية.
الأشكال: قائمة الأشكال
shapes/Shape.java: واجهة الشكل الشائع
package refactoring_guru.prototype.example.shapes;
import java.util.Objects;
public abstract class Shape {
public int x;
public int y;
public String color;
public Shape() {
}
public Shape(Shape target) {
if (target != null) {
this.x = target.x;
this.y = target.y;
this.color = target.color;
}
}
public abstract Shape clone();
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Shape)) return false;
Shape shape2 = (Shape) object2;
return shape2.x == x && shape2.y == y && Objects.equals(shape2.color, color);
}
}
shapes/Circle.java: شكل بسيط
package refactoring_guru.prototype.example.shapes;
public class Circle extends Shape {
public int radius;
public Circle() {
}
public Circle(Circle target) {
super(target);
if (target != null) {
this.radius = target.radius;
}
}
@Override
public Shape clone() {
return new Circle(this);
}
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Circle) || !super.equals(object2)) return false;
Circle shape2 = (Circle) object2;
return shape2.radius == radius;
}
}
shapes/Rectangle.java: شكل آخر
package refactoring_guru.prototype.example.shapes;
public class Rectangle extends Shape {
public int width;
public int height;
public Rectangle() {
}
public Rectangle(Rectangle target) {
super(target);
if (target != null) {
this.width = target.width;
this.height = target.height;
}
}
@Override
public Shape clone() {
return new Rectangle(this);
}
@Override
public boolean equals(Object object2) {
if (!(object2 instanceof Rectangle) || !super.equals(object2)) return false;
Rectangle shape2 = (Rectangle) object2;
return shape2.width == width && shape2.height == height;
}
}
Demo.java: مثال للاستنساخ
package refactoring_guru.prototype.example;
import refactoring_guru.prototype.example.shapes.Circle;
import refactoring_guru.prototype.example.shapes.Rectangle;
import refactoring_guru.prototype.example.shapes.Shape;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
List<Shape> shapesCopy = new ArrayList<>();
Circle circle = new Circle();
circle.x = 10;
circle.y = 20;
circle.radius = 15;
shapes.add(circle);
Circle anotherCircle = (Circle) circle.clone();
shapes.add(anotherCircle);
Rectangle rectangle = new Rectangle();
rectangle.width = 10;
rectangle.height = 20;
shapes.add(rectangle);
cloneAndCompare(shapes, shapesCopy);
}
private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {
for (Shape shape : shapes) {
shapesCopy.add(shape.clone());
}
for (int i = 0; i < shapes.size(); i++) {
if (shapes.get(i) != shapesCopy.get(i)) {
System.out.println(i + ": Shapes are different objects (yay!)");
if (shapes.get(i).equals(shapesCopy.get(i))) {
System.out.println(i + ": And they are identical (yay!)");
} else {
System.out.println(i + ": But they are not identical (booo!)");
}
} else {
System.out.println(i + ": Shape objects are the same (booo!)");
}
}
}
}
OutputDemo.txt: نتائج التنفيذ
0: Shapes are different objects (yay!)
0: And they are identical (yay!)
1: Shapes are different objects (yay!)
1: And they are identical (yay!)
2: Shapes are different objects (yay!)
2: And they are identical (yay!)
سجل النموذج الأولي
تستطيع استخدام سجل مركزي (أو مصنع) للنموذج الأولي يحتوي على مجموعة من كائنات نموذج أولي مسبقة التعريف، وهكذا تستطيع استرجاع كائنات جديدة من المصنع من خلال تمرير أسمائها أو أي معامِلات أخرى. سيبحث المصنع عندها عن نموذج أولي مناسب ويستنسخه ثم يعيد نسخة منه إليك.
الذاكرة المؤقتة Cache
cache/BundledShapeCache.java: مصنع النموذج الأولي
package refactoring_guru.prototype.caching.cache;
import refactoring_guru.prototype.example.shapes.Circle;
import refactoring_guru.prototype.example.shapes.Rectangle;
import refactoring_guru.prototype.example.shapes.Shape;
import java.util.HashMap;
import java.util.Map;
public class BundledShapeCache {
private Map<String, Shape> cache = new HashMap<>();
public BundledShapeCache() {
Circle circle = new Circle();
circle.x = 5;
circle.y = 7;
circle.radius = 45;
circle.color = "Green";
Rectangle rectangle = new Rectangle();
rectangle.x = 6;
rectangle.y = 9;
rectangle.width = 8;
rectangle.height = 10;
rectangle.color = "Blue";
cache.put("Big green circle", circle);
cache.put("Medium blue rectangle", rectangle);
}
public Shape put(String key, Shape shape) {
cache.put(key, shape);
return shape;
}
public Shape get(String key) {
return cache.get(key).clone();
}
}
Demo.java: مثال للاستنساخ
package refactoring_guru.prototype.caching;
import refactoring_guru.prototype.caching.cache.BundledShapeCache;
import refactoring_guru.prototype.example.shapes.Shape;
public class Demo {
public static void main(String[] args) {
BundledShapeCache cache = new BundledShapeCache();
Shape shape1 = cache.get("Big green circle");
Shape shape2 = cache.get("Medium blue rectangle");
Shape shape3 = cache.get("Medium blue rectangle");
if (shape1 != shape2 && !shape1.equals(shape2)) {
System.out.println("Big green circle != Medium blue rectangle (yay!)");
} else {
System.out.println("Big green circle == Medium blue rectangle (booo!)");
}
if (shape2 != shape3) {
System.out.println("Medium blue rectangles are two different objects (yay!)");
if (shape2.equals(shape3)) {
System.out.println("And they are identical (yay!)");
} else {
System.out.println("But they are not identical (booo!)");
}
} else {
System.out.println("Rectangle objects are the same (booo!)");
}
}
}
OutputDemo.txt: نتائج التنفيذ
Big green circle != Medium blue rectangle (yay!)
Medium blue rectangles are two different objects (yay!)
And they are identical (yay!)
الاستخدام في #C
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في لغة #C مع واجهة Cloneable
.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل clone
أو copy
.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
program.cs: مثال هيكلي
using System;
namespace RefactoringGuru.DesignPatterns.Prototype.Conceptual
{
public class Person
{
public int Age;
public DateTime BirthDate;
public string Name;
public IdInfo IdInfo;
public Person ShallowCopy()
{
return (Person) this.MemberwiseClone();
}
public Person DeepCopy()
{
Person clone = (Person) this.MemberwiseClone();
clone.IdInfo = new IdInfo(IdInfo.IdNumber);
clone.Name = String.Copy(Name);
return clone;
}
}
public class IdInfo
{
public int IdNumber;
public IdInfo(int idNumber)
{
this.IdNumber = idNumber;
}
}
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Age = 42;
p1.BirthDate = Convert.ToDateTime("1977-01-01");
p1.Name = "Jack Daniels";
p1.IdInfo = new IdInfo(666);
// P2 وأسندها إلى P1 خذ نسخة سطحية من .
Person p2 = p1.ShallowCopy();
// P3 وأسندها إلى P1 خذ نسخة عميقة من .
Person p3 = p1.DeepCopy();
// P3 و P2 و P1 اعرض قيم.
Console.WriteLine("Original values of p1, p2, p3:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values:");
DisplayValues(p2);
Console.WriteLine(" p3 instance values:");
DisplayValues(p3);
// P3 و P2 و P1واعرض قيم P1 غيّر قيمة الخصائص لـ
p1.Age = 32;
p1.BirthDate = Convert.ToDateTime("1900-01-01");
p1.Name = "Frank";
p1.IdInfo.IdNumber = 7878;
Console.WriteLine("\nValues of p1, p2 and p3 after changes to p1:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values (reference values have changed):");
DisplayValues(p2);
Console.WriteLine(" p3 instance values (everything was kept the same):");
DisplayValues(p3);
}
public static void DisplayValues(Person p)
{
Console.WriteLine(" Name: {0:s}, Age: {1:d}, BirthDate: {2:MM/dd/yy}",
p.Name, p.Age, p.BirthDate);
Console.WriteLine(" ID#: {0:d}", p.IdInfo.IdNumber);
}
}
}
Output.txt: المخرجات
Original values of p1, p2, p3:
p1 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
p2 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
p3 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
Values of p1, p2 and p3 after changes to p1:
p1 instance values:
Name: Frank, Age: 32, BirthDate: 01/01/00
ID#: 7878
p2 instance values (reference values have changed):
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 7878
p3 instance values (everything was kept the same):
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
الاستخدام في لغة PHP
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح في لغة PHP مباشرة، يمكنك استخدام كلمة clone
المفتاحية لإنشاء نسخة طبق الأصل من كائن ما، أما لإضافة دعم الاستنساخ إلى فئة ما فستحتاج إلى استخدام أسلوب clone__
.
مثال: بنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
PrototypeStructural.php: مثال هيكلي
<?php
namespace RefactoringGuru\Prototype\Structural;
/**
* فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع.
*/
class Prototype
{
public $primitive;
public $component;
public $circularReference;
/**
* لديها دعم مدمج فيها للاستنساخ، تستطيع استنساخ كائن ما دون تحديد PHP
* أساليب خاصة طالما أن لديه حقولًا من الأنواع الأساسية.
* تحتفظ الحقول الحاوية للكائنات بمراجعها في الكائن المستنسخ، لهذا
* قد ترغب باستنساخ تلك الكائنات المرجعية أيضًا، تستطيع فعل ذلك
* '__clone()' في الأسلوب الخاص
*/
public function __clone()
{
$this->component = clone $this->component;
// "backreference" تحتاج إلى معاملة خاصة في حالة استنساخ كائن له مرجعية
// مع كائن آخر، فبعد تمام الاستنساخ يجب أن يشير (nested) خلفية ومتداخل
// الكائن المتداخل إلى الكائن المستنسَخ بدلًا من الكائن الأصلي.
$this->circularReference = clone $this->circularReference;
$this->circularReference->prototype = $this;
}
}
class ComponentWithBackReference
{
public $prototype;
/**
* لاحظ ان المنشئ لن ينفَّذ خلال الاستنساخ، فإن كان لديك منطق معقد داخل المنشئ
* أيضًا `__clone` فقد تضطر إلى تنفيذه داخل أسلوب
*/
public function __construct(Prototype $prototype)
{
$this->prototype = $prototype;
}
}
/**
* شيفرة العميل.
*/
function clientCode()
{
$p1 = new Prototype;
$p1->primitive = 245;
$p1->component = new \DateTime;
$p1->circularReference = new ComponentWithBackReference($p1);
$p2 = clone $p1;
if ($p1->primitive === $p2->primitive) {
echo "Primitive field values have been carried over to a clone. Yay!\n";
} else {
echo "Primitive field values have not been copied. Booo!\n";
}
if ($p1->component === $p2->component) {
echo "Simple component has not been cloned. Booo!\n";
} else {
echo "Simple component has been cloned. Yay!\n";
}
if ($p1->circularReference === $p2->circularReference) {
echo "Component with back reference has not been cloned. Booo!\n";
} else {
echo "Component with back reference has been cloned. Yay!\n";
}
if ($p1->circularReference->prototype === $p2->circularReference->prototype) {
echo "Component with back reference is linked to original object. Booo!\n";
} else {
echo "Component with back reference is linked to the clone. Yay!\n";
}
}
clientCode();
Output.txt: المخرجات
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
مثال: حالة حقيقية
يوفر نمط النموذج الأولي طريقة سهلة لتكرار الكائنات الموجودة مسبقًا بدلًا من إعادة إنشائها ونسخ كل حقولها مباشرة، وهذا المنظور المباشر لا يربطك بفئات الكائنات المستنسَخة فقط، بل يمنعك أيضًا من نسخ محتويات الحقول الخاصة. أما نمط النموذج الأولي فيسمح لك بإجراء الاستنساخ داخل سياق الفئة المستنسَخة حيث يكون الوصول إلى الحقول الخاصة للفئة غير محظور.
ويوضح هذا المثال التالي كيف تستنسخ كائن صفحة معقد باستخدام نمط النموذج الأولي، وتحتوي فئة الصفحة على حقول خاصة كثيرة ستنتقل إلى الكائن المستنسخ بفضل نمط النموذج الأولي.
PrototypeRealWorld.php: حالة حقيقية
<?php
namespace RefactoringGuru\Prototype\RealWorld;
/**
* نمط النموذج الأولي
*
* الهدف: إنتاج كائنات جديدة من خلال نسخ الكائنات الحالية دون كشف بنيتها
* الداخلية.
*
* يوفر نمط النموذج الأولي طريقة سهلة لتكرار الكائنات الموجودة مسبقًا بدلًا من
* إعادة إنشائها ونسخ كل حقولها مباشرة، وهذا المنظور المباشر لا يربطك بفئات
* الكائنات المستنسَخة فقط، بل يمنعك أيضًا من نسخ محتويات الحقول الخاصة. أما نمط
* النموذج الأولي فيسمح لك بإجراء الاستنساخ داخل سياق الفئة المستنسَخة حيث يكون
* الوصول إلى الحقول الخاصة للفئة غير محظور.
* ويوضح هذا المثال التالي كيف تستنسخ كائن صفحة معقد باستخدام نمط النموذج
* الأولي، وتحتوي فئة الصفحة على حقول خاصة كثيرة ستنتقل إلى الكائن المستنسخ بفضل
* نمط النموذج الأولي.
*/
/**
* النموذج الأولي.
*/
class Page
{
private $title;
private $body;
/**
* @var Author
*/
private $author;
private $comments = [];
/**
* @var \DateTime
*/
private $date;
// حقول خاصة +100.
public function __construct(string $title, string $body, Author $author)
{
$this->title = $title;
$this->body = $body;
$this->author = $author;
$this->author->addToPage($this);
$this->date = new \DateTime;
}
public function addComment(string $comment): void
{
$this->comments[] = $comment;
}
/**
* تستطيع التحكم في البيانات التي تريد نقلها إلى الكائن المستنسَخ.
*
* فمثلًا، عند استنساخ صفحة:
* - "Copy of ..."فإنها تحصل على عنوان.
* - يظل مؤلف الصفحة كما هو، لهذا نترك المرجع إلى الكائن الحالي أثناء إضافة
* الصفحة المستنسخة إلى قائمة صفحات المؤلف.
* - لا ننقل التعليقات من الصفحة القديمة.
* - نلحق كائن تاريخ جديد إلى الصفحة.
*/
public function __clone()
{
$this->title = "Copy of " . $this->title;
$this->author->addToPage($this);
$this->comments = [];
$this->date = new \DateTime;
}
}
class Author
{
private $name;
/**
* @var Page[]
*/
private $pages = [];
public function __construct(string $name)
{
$this->name = $name;
}
public function addToPage(Page $page): void
{
$this->pages[] = $page;
}
}
/**
* شيفرة العميل.
*/
function clientCode()
{
$author = new Author("John Smith");
$page = new Page("Tip of the day", "Keep calm and carry on.", $author);
// ...
$page->addComment("Nice tip, thanks!");
// ...
$draft = clone $page;
echo "Dump of the clone. Note that the author is now referencing two objects.\n\n";
print_r($draft);
}
clientCode();
Output.txt: المخرجات
Dump of the clone. Note that the author is now referencing two objects.
RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Copy of Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
(
[name:RefactoringGuru\Prototype\RealWorld\Author:private] => John Smith
[pages:RefactoringGuru\Prototype\RealWorld\Author:private] => Array
(
[0] => RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
*RECURSION*
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
[0] => Nice tip, thanks!
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306237
[timezone_type] => 3
[timezone] => UTC
)
)
[1] => RefactoringGuru\Prototype\RealWorld\Page Object
*RECURSION*
)
)
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306272
[timezone_type] => 3
[timezone] => UTC
)
)
الاستخدام في لغة بايثون
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في لغة بايثون مع واجهة Cloneable
.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل clone
أو copy
.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from datetime import datetime
from copy import deepcopy
from typing import Any
class Prototype:
"""
فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع.
"""
def __init__(self) -> None:
self._primitive = None
self._component = None
self._circular_reference = None
@property
def primitive(self) -> Any:
return self._primitive
@primitive.setter
def primitive(self, value: Any) -> None:
self._primitive = value
@property
def component(self) -> object:
return self._component
@component.setter
def component(self, value: object) -> None:
self._component = value
@property
def circular_reference(self) -> ComponentWithBackReference:
return self._circular_reference
@circular_reference.setter
def circular_reference(self, value: ComponentWithBackReference) -> None:
self._circular_reference = value
def clone(self) -> Prototype:
self.component = deepcopy(self.component)
# "backreference" تحتاج إلى معاملة خاصة في حالة استنساخ كائن له مرجعية
# مع كائن آخر، فبعد تمام الاستنساخ يجب أن يشير (nested) خلفية ومتداخل
# الكائن المتداخل إلى الكائن المستنسَخ بدلًا من الكائن الأصلي.
self.circular_reference = deepcopy(self.circular_reference)
self.circular_reference.prototype = self
return deepcopy(self)
class ComponentWithBackReference:
def __init__(self, prototype: Prototype):
self._prototype = prototype
@property
def prototype(self) -> Prototype:
return self._prototype
@prototype.setter
def prototype(self, value: Prototype) -> None:
self._prototype = value
if __name__ == "__main__":
# شيفرة العميل.
p1 = Prototype()
p1.primitive = 245
p1.component = datetime.now()
p1.circular_reference = ComponentWithBackReference(p1)
p2 = p1.clone()
if p1.primitive is p2.primitive:
print("Primitive field values have been carried over to a clone. Yay!")
else:
print("Primitive field values have not been copied. Booo!")
if p1.component is p2.component:
print("Simple component has not been cloned. Booo!")
else:
print("Simple component has been cloned. Yay!")
if p1.circular_reference is p2.circular_reference:
print("Component with back reference has not been cloned. Booo!")
else:
print("Component with back reference has been cloned. Yay!")
if p1.circular_reference.prototype is p2.circular_reference.prototype:
print("Component with back reference is linked to original object. Booo!", end="")
else:
print("Component with back reference is linked to the clone. Yay!", end="")
Output.txt: نتائج التنفيذ
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
الاستخدام في لغة روبي
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في لغة روبي مع واجهة Cloneable
.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل clone
أو copy
.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع.
class Prototype
attr_accessor :primitive, :component, :circular_reference
def initialize
@primitive = nil
@component = nil
@circular_reference = nil
end
# @return [Prototype]
def clone
@component = deep_copy(@component)
# "backreference" تحتاج إلى معاملة خاصة في حالة استنساخ كائن له مرجعية
# مع كائن آخر، فبعد تمام الاستنساخ يجب أن يشير (nested) خلفية ومتداخل
# الكائن المتداخل إلى الكائن المستنسَخ بدلًا من الكائن الأصلي.
@circular_reference = deep_copy(@circular_reference)
@circular_reference.prototype = self
deep_copy(self)
end
# هو الأسلوب المعتاد لصنع نسخة عميقة، لكنه بطيء وغير كفء deep_copy
# خاصة عند العمل مع التطبيقات الحقيقية gem لهذا، استخدم.
private def deep_copy(object)
Marshal.load(Marshal.dump(object))
end
end
class ComponentWithBackReference
attr_accessor :prototype
# @param [Prototype] prototype
def initialize(prototype)
@prototype = prototype
end
end
# شيفرة العميل.
p1 = Prototype.new
p1.primitive = 245
p1.component = Time.now
p1.circular_reference = ComponentWithBackReference.new(p1)
p2 = p1.clone
if p1.primitive == p2.primitive
puts 'Primitive field values have been carried over to a clone. Yay!'
else
puts 'Primitive field values have not been copied. Booo!'
end
if p1.component.equal?(p2.component)
puts 'Simple component has not been cloned. Booo!'
else
puts 'Simple component has been cloned. Yay!'
end
if p1.circular_reference.equal?(p2.circular_reference)
puts 'Component with back reference has not been cloned. Booo!'
else
puts 'Component with back reference has been cloned. Yay!'
end
if p1.circular_reference.prototype.equal?(p2.circular_reference.prototype)
print 'Component with back reference is linked to original object. Booo!'
else
print 'Component with back reference is linked to the clone. Yay!'
end
output.txt: نتائج التنفيذ
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
الاستخدام في لغة Swift
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في لغة Swift مع واجهة Cloneable
.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل clone
أو copy
.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
Example.swift: مثال تصوري
import XCTest
/// تدعم الاستنساخ إذ هو مضمن فيها، ولكي تضيف ذلك الدعم إلى فئتك Swift لغة
/// copy في تلك الفئة وتوفير الاستخدام لأسلوب NSCopying فإنك تحتاج إلى استخدام بروتوكول.
class BaseClass: NSCopying, Equatable {
private var intValue = 1
private var stringValue = "Value"
required init(intValue: Int = 1, stringValue: String = "Value") {
self.intValue = intValue
self.stringValue = stringValue
}
/// MARK: - NSCopying
func copy(with zone: NSZone? = nil) -> Any {
let prototype = type(of: self).init()
prototype.intValue = intValue
prototype.stringValue = stringValue
print("Values defined in BaseClass have been cloned!")
return prototype
}
/// MARK: - Equatable
static func == (lhs: BaseClass, rhs: BaseClass) -> Bool {
return lhs.intValue == rhs.intValue && lhs.stringValue == rhs.stringValue
}
}
/// لتنسخ بياناتها داخل copy تستطيع الفئات الفرعية أن تتجاوز أسلوب
/// الكائن الناتج، لكن يجب أن تستدعي الأسلوب الأساسي أولًا.
class SubClass: BaseClass {
private var boolValue = true
func copy() -> Any {
return copy(with: nil)
}
override func copy(with zone: NSZone?) -> Any {
guard let prototype = super.copy(with: zone) as? SubClass else {
return SubClass() // oops
}
prototype.boolValue = boolValue
print("Values defined in SubClass have been cloned!")
return prototype
}
}
/// شيفرة العميل.
class Client {
// ...
static func someClientCode() {
let original = SubClass(intValue: 2, stringValue: "Value2")
guard let copy = original.copy() as? SubClass else {
XCTAssert(false)
return
}
/// للمزيد من التفاصيل Equatable انظر استخدام بروتوكول.
XCTAssert(copy == original)
print("The original object is equal to the copied object!")
}
// ...
}
/// لنرى الآن كيف سيعمل كل ذلك.
class PrototypeConceptual: XCTestCase {
func testPrototype_NSCopying() {
Client.someClientCode();
}
}
Output.txt: نتائج التنفيذ
Values defined in BaseClass have been cloned!
Values defined in SubClass have been cloned!
The original object is equal to the copied object!
مثال واقعي
Example.swift: مثال واقعي
import XCTest
class PrototypeRealWorld: XCTestCase {
func testPrototypeRealWorld() {
let author = Author(id: 10, username: "Ivan_83")
let page = Page(title: "My First Page", contents: "Hello world!", author: author)
page.add(comment: Comment(message: "Keep it up!"))
/// يعيد أي شيء NSCopying بما أن.
guard let anotherPage = page.copy() as? Page else {
XCTFail("Page was not copied")
return
}
/// يجب أن تكون التعليقات فارغة بما أن الصفحة جديدة.
XCTAssert(anotherPage.comments.isEmpty)
/// لاحظ أن المؤلف يشير الآن إلى كائنين.
XCTAssert(author.pagesCount == 2)
print("Original title: " + page.title)
print("Copied title: " + anotherPage.title)
print("Count of pages: " + String(author.pagesCount))
}
}
private class Author {
private var id: Int
private var username: String
private var pages = [Page]()
init(id: Int, username: String) {
self.id = id
self.username = username
}
func add(page: Page) {
pages.append(page)
}
var pagesCount: Int {
return pages.count
}
}
private class Page: NSCopying {
private(set) var title: String
private(set) var contents: String
private weak var author: Author?
private(set) var comments = [Comment]()
init(title: String, contents: String, author: Author?) {
self.title = title
self.contents = contents
self.author = author
author?.add(page: self)
}
func add(comment: Comment) {
comments.append(comment)
}
/// MARK: - NSCopying
func copy(with zone: NSZone? = nil) -> Any {
return Page(title: "Copy of '" + title + "'", contents: contents, author: author)
}
}
private struct Comment {
let date = Date()
let message: String
}
Output.txt: نتائج التنفيذ
Original title: My First Page
Copied title: Copy of 'My First Page'
Count of pages: 2
الاستخدام في لغة TypeScript
المستوى: ★ ☆ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: نمط النموذج الأولي متاح افتراضيًا في لغة TypeScript مع واجهة Cloneable
.
يمكن ملاحظة النموذج الأولي من خلال أساليب مثل clone
أو copy
.
مثال: بُنية النمط
يوضح هذا المثال بنية نمط النموذج الأولي، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* فئة المثال هذه لديها القدرة على الاستنساخ، سنرى كيف تُنسخ قيم الحقول مختلفة الأنواع.
*/
class Prototype {
public primitive: any;
public component: object;
public circularReference: ComponentWithBackReference;
public clone(): this {
const clone = Object.create(this);
clone.component = Object.create(this.component);
// يحتاج استنساخ كائن به كائن متداخل مع مرجع خلفي إلى معاملة خاصة،
// ذلك أنه بعد انتهاء الاستنساخ فإن الكائن المتداخل يجب أن يشير إلى
// الكائن المستنسَخ، بدلًا من الكائن الأصلي.
// مفيدًا ها هنا Spread قد يكون معامِل.
clone.circularReference = {
...this.circularReference,
prototype: { ...this },
};
return clone;
}
}
class ComponentWithBackReference {
public prototype;
constructor(prototype: Prototype) {
this.prototype = prototype;
}
}
/**
* شيفرة العميل.
*/
function clientCode() {
const p1 = new Prototype();
p1.primitive = 245;
p1.component = new Date();
p1.circularReference = new ComponentWithBackReference(p1);
const p2 = p1.clone();
if (p1.primitive === p2.primitive) {
console.log('Primitive field values have been carried over to a clone. Yay!');
} else {
console.log('Primitive field values have not been copied. Booo!');
}
if (p1.component === p2.component) {
console.log('Simple component has not been cloned. Booo!');
} else {
console.log('Simple component has been cloned. Yay!');
}
if (p1.circularReference === p2.circularReference) {
console.log('Component with back reference has not been cloned. Booo!');
} else {
console.log('Component with back reference has been cloned. Yay!');
}
if (p1.circularReference.prototype === p2.circularReference.prototype) {
console.log('Component with back reference is linked to original object. Booo!');
} else {
console.log('Component with back reference is linked to the clone. Yay!');
}
}
clientCode();
Output.txt: نتائج التنفيذ
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
انظر أيضًا
- نمط المزخرف Decorator.
- نمط المركَّب Composite.
- نمط الورقة المفردة Singleton.
- نمط المصنع المجرَّد Abstract Factory.