الفرق بين المراجعتين لصفحة: «Design Patterns/decorator»
أسامه-دمراني (نقاش | مساهمات) تعديل تنسيق ←قابلية الاستخدام |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(5 مراجعات متوسطة بواسطة 4 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:نمط | <noinclude>{{DISPLAYTITLE:نمط المزخرف Decorator}}</noinclude> | ||
نمط المزخرِف هو نمط تصميم هيكلي يضيف سلوكيات جديدة إلى الكائنات بوضعها داخل كائنات تغليف خاصة تحتوي تلك السلوكيات. | نمط المزخرِف هو نمط تصميم هيكلي يضيف سلوكيات جديدة إلى الكائنات بوضعها داخل كائنات تغليف خاصة تحتوي تلك السلوكيات. | ||
سطر 27: | سطر 27: | ||
== مثال واقعي == | == مثال واقعي == | ||
ارتداء الملابس مثال على استخدام المزخرِفات، فأنت تغلف نفسك بكنزة مثلًا طلبًا للدفء في البرد اليسير، ثم تضيف فوقها معطفًا إن اشتد البرد، أما إن كانت تمطر فستلبس معطفًا خاصًا بالمطر، وكل تلك القطع توسع سلوكك الأساسي لكنها ليست | ارتداء الملابس مثال على استخدام المزخرِفات، فأنت تغلف نفسك بكنزة مثلًا طلبًا للدفء في البرد اليسير، ثم تضيف فوقها معطفًا إن اشتد البرد، أما إن كانت تمطر فستلبس معطفًا خاصًا بالمطر، وكل تلك القطع توسع سلوكك الأساسي لكنها ليست عنصرًا منك، وتستطيع خلع أي قطعة ملابس في أي وقت تشعر فيه أنك لم تحتاج إليها. | ||
== البُنية == | == البُنية == | ||
[[ملف:dpd.structure-indexed.png|تصغير|154x154px|(ش.7) ]] | [[ملف:dpd.structure-indexed.png|تصغير|154x154px|(ش.7) ]] | ||
# يصرح ''' | # يصرح '''العنصر (Component)''' عن واجهة مشتركة لكل من المغلِّفات والكائنات المغلَّفة. | ||
# ''' | # '''العنصر الحقيقي (Concrete Component)''' هو فئة الكائنات يتم تغليفها، وتعرِّف السلوك الأساسي الذي يمكن تغييره بواسطة المزخرِفات. | ||
# '''فئة المزخرف الأساسي (Base Decorator)''' لها حقل للإشارة إلى كائن مغلَّف، ويجب أن يصرَّح عن نوع الحقل على أنه واجهة | # '''فئة المزخرف الأساسي (Base Decorator)''' لها حقل للإشارة إلى كائن مغلَّف، ويجب أن يصرَّح عن نوع الحقل على أنه واجهة العنصر كي يستطيع احتواء كلًا من العناصر الحقيقية والمزخرِفات. ويفوِّض المزخرِف الأساسي العمليات كلها إلى الكائن المغلَّف. | ||
# تعرِّف '''المزخرِفات الحقيقية (Concrete Decorators)''' سلوكيات إضافية يمكن أن تضاف إلى | # تعرِّف '''المزخرِفات الحقيقية (Concrete Decorators)''' سلوكيات إضافية يمكن أن تضاف إلى العناصر بديناميكية، وتتخطى المزخرِفات الحقيقية أساليب المزخرِف الأساسي وتنفذ أسلوبها قبل أو بعد استدعاء الأسلوب الأم (Parent Behavior). | ||
# '''العميل (Client)''' يستطيع تغليف | # '''العميل (Client)''' يستطيع تغليف العناصر في طبقات متعددة من المزخرِفات طالما أنها تعمل مع كل الكائنات من خلال واجهة العنصر (Component Interface). | ||
== مثال توضيحي == | == مثال توضيحي == | ||
سطر 42: | سطر 42: | ||
* تمر البيانات بعد '''قراءتها من القرص''' مباشرة على نفس المزخرِفات التي ستفك ضغطها وتفك تشفيرها. | * تمر البيانات بعد '''قراءتها من القرص''' مباشرة على نفس المزخرِفات التي ستفك ضغطها وتفك تشفيرها. | ||
تستخدم المزخرِفات وفئةُ مصدرِ البيانات نفس الواجهة التي تجعلها كلها قابلة للتعديل بشكل متبادل (interchangeable) في شيفرة العميل.<syntaxhighlight lang="java"> | تستخدم المزخرِفات وفئةُ مصدرِ البيانات نفس الواجهة التي تجعلها كلها قابلة للتعديل بشكل متبادل (interchangeable) في شيفرة العميل.<syntaxhighlight lang="java"> | ||
// تعرِّف | // تعرِّف واجهةُ العنصر العملياتَ التي يمكن تغييرها بواسطة المزخرفات. | ||
interface DataSource is | interface DataSource is | ||
method writeData(data) | method writeData(data) | ||
method readData():data | method readData():data | ||
// تضيف | // تضيف العناصر الحقيقية استخدامات افتراضية للعمليات، قد تكون هناك صور مختلفة لهذه الفئات | ||
// في برنامج ما | // في برنامج ما | ||
class FileDataSource implements DataSource is | class FileDataSource implements DataSource is | ||
سطر 58: | سطر 58: | ||
// اقرأ البيانات من الملف. | // اقرأ البيانات من الملف. | ||
// تتبع فئة المزخرِف الأساسي نفس الواجهة | // تتبع فئة المزخرِف الأساسي نفس الواجهة كالعناصر الأخرى، والقرص الأساسي لهذه الفئة هو تعريف واجهة التغليف | ||
// لكل المزخرِفات الحقيقية. قد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | // لكل المزخرِفات الحقيقية. قد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين العنصر المغلَّف | ||
// ووسائل بدئها. | // ووسائل بدئها. | ||
سطر 68: | سطر 68: | ||
wrappee = source | wrappee = source | ||
// يفوِّض المزخرِف الأساسي كل المهام إلى | // يفوِّض المزخرِف الأساسي كل المهام إلى العنصر المغلَّف، ويمكن إضافة السلوكيات الإضافية من المزخرِفات | ||
// الحقيقية. | // الحقيقية. | ||
method writeData(data) is | method writeData(data) is | ||
سطر 161: | سطر 161: | ||
== كيفية الاستخدام == | == كيفية الاستخدام == | ||
# تأكد أن نطاقك التجاري يمكن تمثيله | # تأكد أن نطاقك التجاري يمكن تمثيله كعنصر أساسي مع عدة طبقات اختيارية فوقه. | ||
# اكتشف أي الأساليب التي تشترك فيها الطبقات الخيارية مع | # اكتشف أي الأساليب التي تشترك فيها الطبقات الخيارية مع العنصر الأساسي، ومن ثم أنشئ واجهة للعنصر وصرح عن تلك الأساليب هناك. | ||
# أنشئ فئة | # أنشئ فئة عنصر حقيقي وعرَّف السلوك الأساسي فيها. | ||
# أنشئ فئة مزخرِف أساسي، وينبغي أن يكون فيها حقل لتخزين مرجع إلى الكائن المُغلَّف، ويجب أن يُصرَّح عن الحقل مع نوع واجهة | # أنشئ فئة مزخرِف أساسي، وينبغي أن يكون فيها حقل لتخزين مرجع إلى الكائن المُغلَّف، ويجب أن يُصرَّح عن الحقل مع نوع واجهة العنصر (Component Interface Type) ليسمح بالربط مع العناصر الحقيقية والمزخرِفات، ويجب أن يفوض المزخرِف الأساسي كل المهام إلى الكائن المغلَّف. | ||
# تأكد أن كل الفئات تستخدم واجهة | # تأكد أن كل الفئات تستخدم واجهة العنصر. | ||
# أنشئ مزخرَفات حقيقية بتوسيعها من المزخرِف الأساسي، ويجب أن ينفذ المزخرف الحقيقي سلوكه قبل أو بعد الاستدعاء إلى الأسلوب الأم (Parent Method)، والذي يفوض بدوره المهام إلى الكائن المغلَّف. | # أنشئ مزخرَفات حقيقية بتوسيعها من المزخرِف الأساسي، ويجب أن ينفذ المزخرف الحقيقي سلوكه قبل أو بعد الاستدعاء إلى الأسلوب الأم (Parent Method)، والذي يفوض بدوره المهام إلى الكائن المغلَّف. | ||
# يجب أن تكون شيفرة العميل مسؤولة عن إنشاء المزخرِفات وترتيبها بالطريقة التي يحتاجها العميل. | # يجب أن تكون شيفرة العميل مسؤولة عن إنشاء المزخرِفات وترتيبها بالطريقة التي يحتاجها العميل. | ||
سطر 192: | سطر 192: | ||
== الاستخدام في لغة جافا == | == الاستخدام في لغة جافا == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). إليك بعض الأمثلة على المزخرِف في مكتبات جافا: | '''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). إليك بعض الأمثلة على المزخرِف في مكتبات جافا: | ||
سطر 218: | سطر 218: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== decorators/FileDataSource.java: قارئ/كاتب بيانات بسيط ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
package refactoring_guru.decorator.example.decorators; | package refactoring_guru.decorator.example.decorators; | ||
سطر 256: | سطر 256: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== decorators/DataSourceDecorator.java: مزخرف أساسي مجرد ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
package refactoring_guru.decorator.example.decorators; | package refactoring_guru.decorator.example.decorators; | ||
سطر 279: | سطر 279: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== decorators/EncryptionDecorator.java: مزخرف التشفير ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
package refactoring_guru.decorator.example.decorators; | package refactoring_guru.decorator.example.decorators; | ||
سطر 319: | سطر 319: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== decorators/CompressionDecorator.java: مزخرف الضغط ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
package refactoring_guru.decorator.example.decorators; | package refactoring_guru.decorator.example.decorators; | ||
سطر 392: | سطر 392: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== Demo.java: شيفرة العميل ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
package refactoring_guru.decorator.example; | package refactoring_guru.decorator.example; | ||
سطر 417: | سطر 417: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===== | ===== OutputDemo.txt: نتائج التنفيذ ===== | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
- Input ---------------- | - Input ---------------- | ||
سطر 432: | سطر 432: | ||
== الاستخدام في لغة #C == | == الاستخدام في لغة #C == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | '''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | ||
سطر 441: | سطر 441: | ||
=== مثال تصوري === | === مثال تصوري === | ||
يوضح هذا المثال بنية | يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | ||
* ما الفئات التي يتكون منها؟ | * ما الفئات التي يتكون منها؟ | ||
* ما الأدوار التي تلعبها هذه الفئات؟ | * ما الأدوار التي تلعبها هذه الفئات؟ | ||
* كيف ترتبط عناصر النمط ببعضها؟ | * كيف ترتبط عناصر النمط ببعضها؟ | ||
==== Program.cs: | ==== Program.cs: مثال تصوري ==== | ||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
using System; | using System; | ||
سطر 452: | سطر 452: | ||
namespace RefactoringGuru.DesignPatterns.Composite.Conceptual | namespace RefactoringGuru.DesignPatterns.Composite.Conceptual | ||
{ | { | ||
// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة | // العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي. | ||
public abstract class Component | public abstract class Component | ||
{ | { | ||
سطر 458: | سطر 458: | ||
} | } | ||
// تقدم | // تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات. | ||
class ConcreteComponent : Component | class ConcreteComponent : Component | ||
{ | { | ||
سطر 467: | سطر 467: | ||
} | } | ||
// تتبع فئة المزخرف الأساسي نفس واجهة | // تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة | ||
// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | // التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف | ||
// وكذلك أساليب استدعائه. | // وكذلك أساليب استدعائه. | ||
abstract class Decorator : Component | abstract class Decorator : Component | ||
سطر 484: | سطر 484: | ||
} | } | ||
// يفوض المزخرِف كل المهام إلى | // يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | ||
public override string Operation() | public override string Operation() | ||
{ | { | ||
سطر 529: | سطر 529: | ||
public class Client | public class Client | ||
{ | { | ||
// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة | // تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية | ||
// | // للعناصر التي تعمل معها. | ||
public void ClientCode(Component component) | public void ClientCode(Component component) | ||
{ | { | ||
سطر 548: | سطر 548: | ||
Console.WriteLine(); | Console.WriteLine(); | ||
// ...إضافة إلى | // ...إضافة إلى العناصر المزخرفة أيضًا. | ||
// | // | ||
// لاحظ كيف تستطيع المزخرِفات تغليف | // لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات | ||
// الأخرى كذلك. | // الأخرى كذلك. | ||
ConcreteDecoratorA decorator1 = new ConcreteDecoratorA(simple); | ConcreteDecoratorA decorator1 = new ConcreteDecoratorA(simple); | ||
سطر 561: | سطر 561: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I get a simple component: | Client: I get a simple component: | ||
RESULT: ConcreteComponent | RESULT: ConcreteComponent | ||
سطر 571: | سطر 571: | ||
== الاستخدام في لغة PHP == | == الاستخدام في لغة PHP == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | '''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | ||
=== مثال تصوري === | === مثال تصوري === | ||
يوضح هذا المثال بنية | يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | ||
* ما الفئات التي يتكون منها؟ | * ما الفئات التي يتكون منها؟ | ||
* ما الأدوار التي تلعبها هذه الفئات؟ | * ما الأدوار التي تلعبها هذه الفئات؟ | ||
* كيف ترتبط عناصر النمط ببعضها؟ | * كيف ترتبط عناصر النمط ببعضها؟ | ||
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP. | سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة [[PHP]]. | ||
==== index.php: مثال تصوري ==== | ==== index.php: مثال تصوري ==== | ||
سطر 591: | سطر 591: | ||
/** | /** | ||
* العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة | * العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي. | ||
*/ | */ | ||
interface Component | interface Component | ||
سطر 599: | سطر 599: | ||
/** | /** | ||
* تقدم | * تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات. | ||
*/ | */ | ||
class ConcreteComponent implements Component | class ConcreteComponent implements Component | ||
سطر 610: | سطر 610: | ||
/** | /** | ||
* تتبع فئة المزخرف الأساسي نفس واجهة | * تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة | ||
* التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | * التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف | ||
* وكذلك أساليب استدعائه. | * وكذلك أساليب استدعائه. | ||
*/ | */ | ||
سطر 627: | سطر 627: | ||
/** | /** | ||
* يفوض المزخرِف كل المهام إلى | * يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | ||
*/ | */ | ||
public function operation(): string | public function operation(): string | ||
سطر 662: | سطر 662: | ||
/** | /** | ||
* تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة | * تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية | ||
* | * للعناصر التي تعمل معها. | ||
*/ | */ | ||
function clientCode(Component $component) | function clientCode(Component $component) | ||
سطر 675: | سطر 675: | ||
/** | /** | ||
* وهكذا تستطيع شيفرة العميل أن تدعم | * وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة... | ||
*/ | */ | ||
$simple = new ConcreteComponent; | $simple = new ConcreteComponent; | ||
سطر 683: | سطر 683: | ||
/** | /** | ||
* ...إضافة إلى | * ...إضافة إلى العناصر المزخرَفة. | ||
* | * | ||
* لاحظ كيف تستطيع المزخرِفات تغليف | * لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرِفات الأخرى كذلك. | ||
*/ | */ | ||
$decorator1 = new ConcreteDecoratorA($simple); | $decorator1 = new ConcreteDecoratorA($simple); | ||
سطر 693: | سطر 693: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I've got a simple component: | Client: I've got a simple component: | ||
RESULT: ConcreteComponent | RESULT: ConcreteComponent | ||
سطر 707: | سطر 707: | ||
وقد ترغب في السماح بنشر بصيغة Markdown التي ستعالَج قبل أي ترشيح يحدث في HTML، وكل هذه القواعد في الترشيحات يمكن تمثيلها كفئات مزخرِفات منفصلة تكدس بأشكال مختلفة وفقًا لطبيعة المحتوى لديك. | وقد ترغب في السماح بنشر بصيغة Markdown التي ستعالَج قبل أي ترشيح يحدث في HTML، وكل هذه القواعد في الترشيحات يمكن تمثيلها كفئات مزخرِفات منفصلة تكدس بأشكال مختلفة وفقًا لطبيعة المحتوى لديك. | ||
==== | ==== index.php: مثال واقعي ==== | ||
<syntaxhighlight lang="php"> | <syntaxhighlight lang="php"> | ||
<?php | <?php | ||
سطر 714: | سطر 714: | ||
/** | /** | ||
* تصرِّح واجهة | * تصرِّح واجهة العنصر عن أسلوب ترشيح يجب استخدامه من قِبل جميع العناصر الحقيقية | ||
* والمزخرِفات. | * والمزخرِفات. | ||
*/ | */ | ||
سطر 723: | سطر 723: | ||
/** | /** | ||
* | * العنصر الحقيقي عنصر أساسي في الزخرفة إذ يحتوي على النص الأصلي كما هو دون | ||
* ترشيح أو تنسيق. | * ترشيح أو تنسيق. | ||
*/ | */ | ||
سطر 737: | سطر 737: | ||
* لا تحتوي فئة المزخرف الأساسية على أي ترشيح حقيقي أو منطق تنسيق، ووظيفتها الأساسية هي | * لا تحتوي فئة المزخرف الأساسية على أي ترشيح حقيقي أو منطق تنسيق، ووظيفتها الأساسية هي | ||
* تنفيذ بنية الزخرفة التحتية الأساسية: | * تنفيذ بنية الزخرفة التحتية الأساسية: | ||
* حقل لتخزين | * حقل لتخزين عنصر مغلَّف أو مزخرِف آخر، وأسلوب التنسيق الأساسي الذي يفوض العمل إلى الكائن | ||
* المغلَّف. | * المغلَّف. | ||
* تنفَّذ مهمة التنسيق الحقيقية بواسطة الفئات الفرعية. | * تنفَّذ مهمة التنسيق الحقيقية بواسطة الفئات الفرعية. | ||
سطر 754: | سطر 754: | ||
/** | /** | ||
* يفوض المزخرِف المهام الأساسية إلى | * يفوض المزخرِف المهام الأساسية إلى العنصر المغلَّف. | ||
*/ | */ | ||
public function formatText(string $text): string | public function formatText(string $text): string | ||
سطر 845: | سطر 845: | ||
/** | /** | ||
* قد تكون شيفرة العميل | * قد تكون شيفرة العميل عنصرًا من موقع حقيقي يخرج محتوىً ينتجه المستخدمون، فلا يعنيها | ||
* إن كانت تحصل على كائن | * إن كانت تحصل على كائن عنصر بسيط أو مزخرَف يما أنها تعمل مع المنسِّقات من خلال واجهة | ||
* | * العنصر . | ||
*/ | */ | ||
function displayCommentAsAWebsite(InputFormat $format, string $text) | function displayCommentAsAWebsite(InputFormat $format, string $text) | ||
سطر 921: | سطر 921: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Website renders comments without filtering (unsafe): | Website renders comments without filtering (unsafe): | ||
Hello! Nice blog post! | Hello! Nice blog post! | ||
سطر 958: | سطر 958: | ||
== الاستخدام في لغة بايثون == | == الاستخدام في لغة بايثون == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط '''المزخرِف''' في شيفرة | '''أمثلة الاستخدام''': يكثر استخدام نمط '''المزخرِف''' في شيفرة بايثون، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | ||
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية. | يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية. | ||
=== مثال تصوري === | === مثال تصوري === | ||
يوضح هذا المثال بنية | يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | ||
* ما الفئات التي يتكون منها؟ | * ما الفئات التي يتكون منها؟ | ||
* ما الأدوار التي تلعبها هذه الفئات؟ | * ما الأدوار التي تلعبها هذه الفئات؟ | ||
* كيف ترتبط عناصر النمط ببعضها؟ | * كيف ترتبط عناصر النمط ببعضها؟ | ||
==== | ==== main.py: مثال تصوري ==== | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
class Component(): | class Component(): | ||
""" | """ | ||
العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة | العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي. | ||
""" | """ | ||
سطر 994: | سطر 994: | ||
class Decorator(Component): | class Decorator(Component): | ||
""" | """ | ||
تتبع فئة المزخرف الأساسي نفس واجهة | تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة | ||
التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف | ||
وكذلك أساليب استدعائه. | وكذلك أساليب استدعائه. | ||
""" | """ | ||
سطر 1٬007: | سطر 1٬007: | ||
def component(self) -> str: | def component(self) -> str: | ||
""" | """ | ||
يفوض المزخرِف كل المهام إلى | يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | ||
""" | """ | ||
سطر 1٬040: | سطر 1٬040: | ||
def client_code(component: Component) -> None: | def client_code(component: Component) -> None: | ||
""" | """ | ||
تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة | تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية. | ||
""" | """ | ||
سطر 1٬051: | سطر 1٬051: | ||
if __name__ == "__main__": | if __name__ == "__main__": | ||
# وهكذا تستطيع شيفرة العميل أن تدعم | # وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة... | ||
simple = ConcreteComponent() | simple = ConcreteComponent() | ||
print("Client: I've got a simple component:") | print("Client: I've got a simple component:") | ||
سطر 1٬057: | سطر 1٬057: | ||
print("\n") | print("\n") | ||
# ...إضافة إلى | # ...إضافة إلى العناصر المزخرفة أيضًا. | ||
# | # | ||
# لاحظ كيف تستطيع المزخرِفات تغليف | # لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك. | ||
decorator1 = ConcreteDecoratorA(simple) | decorator1 = ConcreteDecoratorA(simple) | ||
decorator2 = ConcreteDecoratorB(decorator1) | decorator2 = ConcreteDecoratorB(decorator1) | ||
سطر 1٬066: | سطر 1٬066: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I've got a simple component: | |||
RESULT: ConcreteComponent | |||
Client: Now I've got a decorated component: | |||
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)) | |||
</syntaxhighlight> | |||
== الاستخدام في لغة روبي == | |||
'''المستوى''': ★ ★ ☆ | |||
'''الانتشار''': ★ ★ ☆ | |||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | |||
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== main.rb: مثال تصوري ==== | |||
<syntaxhighlight lang="ruby"> | |||
# العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تحدد واجهة العنصر الأساسي. | |||
class Component | |||
# @return [String] | |||
def operation | |||
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" | |||
end | |||
end | |||
# تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات. | |||
class ConcreteComponent < Component | |||
# @return [String] | |||
def operation | |||
'ConcreteComponent' | |||
end | |||
end | |||
# تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه | |||
# الفئة هو تعريف واجهة التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام | |||
# الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف وكذلك أساليب استدعائه. | |||
class Decorator < Component | |||
attr_accessor :component | |||
# @param [Component] component | |||
def initialize(component) | |||
@component = component | |||
end | |||
# يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | |||
def operation | |||
@component.operation | |||
end | |||
end | |||
# تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما. | |||
class ConcreteDecoratorA < Decorator | |||
# للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم | |||
# الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف. | |||
def operation | |||
"ConcreteDecoratorA(#{@component.operation})" | |||
end | |||
end | |||
# تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف. | |||
class ConcreteDecoratorB < Decorator | |||
# @return [String] | |||
def operation | |||
"ConcreteDecoratorB(#{@component.operation})" | |||
end | |||
end | |||
# تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر، وهكذا | |||
# تظل مستقلة عن الفئات الحقيقية. | |||
def client_code(component) | |||
# ... | |||
print "RESULT: #{component.operation}" | |||
# ... | |||
end | |||
# وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة... | |||
simple = ConcreteComponent.new | |||
puts 'Client: I\'ve got a simple component:' | |||
client_code(simple) | |||
puts "\n\n" | |||
# ...إضافة إلى العناصر المزخرفة أيضًا. | |||
# | |||
# لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك. | |||
decorator1 = ConcreteDecoratorA.new(simple) | |||
decorator2 = ConcreteDecoratorB.new(decorator1) | |||
puts 'Client: Now I\'ve got a decorated component:' | |||
client_code(decorator2) | |||
</syntaxhighlight> | |||
==== output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client: I've got a simple component: | Client: I've got a simple component: | ||
RESULT: ConcreteComponent | RESULT: ConcreteComponent | ||
سطر 1٬076: | سطر 1٬177: | ||
== الاستخدام في لغة Swift == | == الاستخدام في لغة Swift == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | '''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | ||
سطر 1٬085: | سطر 1٬186: | ||
=== مثال تصوري === | === مثال تصوري === | ||
يوضح هذا المثال بنية | يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | ||
* ما الفئات التي يتكون منها؟ | * ما الفئات التي يتكون منها؟ | ||
* ما الأدوار التي تلعبها هذه الفئات؟ | * ما الأدوار التي تلعبها هذه الفئات؟ | ||
* كيف ترتبط عناصر النمط ببعضها؟ | * كيف ترتبط عناصر النمط ببعضها؟ | ||
==== | ==== Example.swift: مثال تصوري ==== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
import XCTest | import XCTest | ||
/// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة | /// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي. | ||
protocol Component { | protocol Component { | ||
سطر 1٬100: | سطر 1٬201: | ||
} | } | ||
/// تقدم | /// تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات. | ||
class ConcreteComponent: Component { | class ConcreteComponent: Component { | ||
سطر 1٬108: | سطر 1٬209: | ||
} | } | ||
/// تتبع فئة المزخرف الأساسي نفس واجهة | /// تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة | ||
/// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | /// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف | ||
/// وكذلك أساليب استدعائه. | /// وكذلك أساليب استدعائه. | ||
class Decorator: Component { | class Decorator: Component { | ||
سطر 1٬119: | سطر 1٬220: | ||
} | } | ||
/// يفوض المزخرِف كل المهام إلى | /// يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | ||
func operation() -> String { | func operation() -> String { | ||
return component.operation() | return component.operation() | ||
سطر 1٬143: | سطر 1٬244: | ||
} | } | ||
/// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة | /// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية | ||
/// | /// للعناصر التي تعمل معها. | ||
class Client { | class Client { | ||
// ... | // ... | ||
سطر 1٬153: | سطر 1٬254: | ||
} | } | ||
/// دعنا نرى كيف ستعمل كل تلك | /// دعنا نرى كيف ستعمل كل تلك العناصر معًا. | ||
class DecoratorConceptual: XCTestCase { | class DecoratorConceptual: XCTestCase { | ||
func testDecoratorStructure() { | func testDecoratorStructure() { | ||
// وهكذا تستطيع شيفرة العميل أن تدعم | // وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة... | ||
print("Client: I've got a simple component") | print("Client: I've got a simple component") | ||
let simple = ConcreteComponent() | let simple = ConcreteComponent() | ||
Client.someClientCode(component: simple) | Client.someClientCode(component: simple) | ||
// ...إضافة إلى | // ...إضافة إلى العناصر المزخرفة أيضًا | ||
// | // | ||
// لاحظ كيف تستطيع المزخرِفات تغليف | // لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك. | ||
let decorator1 = ConcreteDecoratorA(simple) | let decorator1 = ConcreteDecoratorA(simple) | ||
let decorator2 = ConcreteDecoratorB(decorator1) | let decorator2 = ConcreteDecoratorB(decorator1) | ||
سطر 1٬173: | سطر 1٬274: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I've got a simple component | Client: I've got a simple component | ||
Result: ConcreteComponent | Result: ConcreteComponent | ||
سطر 1٬184: | سطر 1٬285: | ||
=== مثال واقعي === | === مثال واقعي === | ||
==== | ==== Example.swift: شيفرة العميل ==== | ||
<syntaxhighlight lang="swift"> | <syntaxhighlight lang="swift"> | ||
import UIKit | import UIKit | ||
سطر 1٬356: | سطر 1٬457: | ||
func clientCode(editor: ImageEditor) { | func clientCode(editor: ImageEditor) { | ||
let image = editor.apply() | let image = editor.apply() | ||
/// | /// كي ترى معاينة للصورة Xcode لاحظ أنك تستطيع إيقاف التنفيذ في . | ||
print("Client: all changes have been applied for \(image)") | print("Client: all changes have been applied for \(image)") | ||
} | } | ||
سطر 1٬367: | سطر 1٬468: | ||
let urlString = "https:// refactoring.guru/images/content-public/logos/logo-new-3x.png" | let urlString = "https:// refactoring.guru/images/content-public/logos/logo-new-3x.png" | ||
/// | /// لاحظ: | ||
/// | /// لا تحمّل الصور بالطريقة أدناه في شيفرة إنتاج. | ||
guard let url = URL(string: urlString) else { | guard let url = URL(string: urlString) else { | ||
سطر 1٬386: | سطر 1٬487: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: set up an editors stack | Client: set up an editors stack | ||
سطر 1٬399: | سطر 1٬500: | ||
== الاستخدام في لغة TypeScript == | == الاستخدام في لغة TypeScript == | ||
'''المستوى''': | '''المستوى''': ★ ★ ☆ | ||
'''الانتشار''': | '''الانتشار''': ★ ★ ☆ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | '''أمثلة الاستخدام''': يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). | ||
سطر 1٬408: | سطر 1٬509: | ||
=== مثال تصوري === | === مثال تصوري === | ||
يوضح هذا المثال بنية | يوضح هذا المثال بنية نمط '''المزخرِف'''، ويركز على إجابة الأسئلة التالية: | ||
* ما الفئات التي يتكون منها؟ | * ما الفئات التي يتكون منها؟ | ||
* ما الأدوار التي تلعبها هذه الفئات؟ | * ما الأدوار التي تلعبها هذه الفئات؟ | ||
* كيف ترتبط عناصر النمط ببعضها؟ | * كيف ترتبط عناصر النمط ببعضها؟ | ||
==== | ==== index.ts: مثال تصوري ==== | ||
<syntaxhighlight lang="typescript"> | <syntaxhighlight lang="typescript"> | ||
/** | /** | ||
* العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة | * العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي. | ||
*/ | */ | ||
interface Component { | interface Component { | ||
سطر 1٬423: | سطر 1٬524: | ||
/** | /** | ||
* تقدم | * تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات. | ||
*/ | */ | ||
class ConcreteComponent implements Component { | class ConcreteComponent implements Component { | ||
سطر 1٬432: | سطر 1٬533: | ||
/** | /** | ||
* تتبع فئة المزخرف الأساسي نفس واجهة | * تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة | ||
* التغليف لكل المزخرِفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | * التغليف لكل المزخرِفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين | ||
* | * عنصر مغلَّف وكذلك أساليب استدعائه. | ||
*/ | */ | ||
class Decorator implements Component { | class Decorator implements Component { | ||
سطر 1٬444: | سطر 1٬545: | ||
/** | /** | ||
* يفوض المزخرِف كل المهام إلى | * يفوض المزخرِف كل المهام إلى العنصر المُغلَّف. | ||
*/ | */ | ||
public operation(): string { | public operation(): string { | ||
سطر 1٬474: | سطر 1٬575: | ||
/** | /** | ||
* تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة | * تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية | ||
* | * للعناصر التي تعمل معها. | ||
*/ | */ | ||
function clientCode(component: Component) { | function clientCode(component: Component) { | ||
سطر 1٬486: | سطر 1٬587: | ||
/** | /** | ||
* وهكذا تستطيع شيفرة العميل أن تدعم | * وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة... | ||
*/ | */ | ||
const simple = new ConcreteComponent(); | const simple = new ConcreteComponent(); | ||
سطر 1٬494: | سطر 1٬595: | ||
/** | /** | ||
* ...إضافة إلى | * ...إضافة إلى العناصر المزخرَفة. | ||
* | * | ||
* لاحظ كيف تستطيع المزخرِفات تغليف | * لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرِفات الأخرى كذلك. | ||
*/ | */ | ||
const decorator1 = new ConcreteDecoratorA(simple); | const decorator1 = new ConcreteDecoratorA(simple); | ||
سطر 1٬504: | سطر 1٬605: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== | ==== Output.txt: نتائج التنفيذ ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I've got a simple component: | Client: I've got a simple component: | ||
RESULT: ConcreteComponent | RESULT: ConcreteComponent | ||
سطر 1٬514: | سطر 1٬615: | ||
== انظر أيضًا == | == انظر أيضًا == | ||
* [[Design Patterns/singleton|نمط المفردة.]] | * [[Design Patterns/singleton|نمط المفردة Singleton.]] | ||
* [[Design Patterns/prototype|نمط النموذج الأولي.]] | * [[Design Patterns/prototype|نمط النموذج الأولي Prototype.]] | ||
* [[Design Patterns/bridge|نمط الجسر.]] | * [[Design Patterns/bridge|نمط الجسر Bridge.]] | ||
== مصادر == | == مصادر == | ||
سطر 1٬522: | سطر 1٬623: | ||
[[تصنيف:Decorator Design Pattern]] | [[تصنيف:Decorator Design Pattern]] | ||
[[تصنيف:Design Patterns]] | [[تصنيف:Design Patterns]] | ||
[[تصنيف:Structural Design Patterns]] |
المراجعة الحالية بتاريخ 10:46، 7 أكتوبر 2022
نمط المزخرِف هو نمط تصميم هيكلي يضيف سلوكيات جديدة إلى الكائنات بوضعها داخل كائنات تغليف خاصة تحتوي تلك السلوكيات.
المشكلة
تخيل أنك تعمل على مكتبة إشعارات تسمح للبرامج الأخرى بتنبيه مستخدميها إلى الأحداث المهمة، وبنيت النسخة الأولية من المكتبة على فئة Notifier
بها بضعة حقول قليلة، ومنشئ (Constructor) وأسلوب Send
وحيد، ويستطيع الأسلوب قبول وسيط في هيئة رسالة من عميل ويرسلها إلى قائمة من عناوين البريد التي يمررها المنشئ إلى المنبه (Notifier).
ويفترض بتطبيق من طرف ثالث يتصرف كعميل أن ينشئ ويهيئ كائن المنبه مرة واحدة ثم يستخدمه في كل مرة يحدث شيء مهم -انظر (ش.1)-، وستدرك في مرحلة ما أن مستخدمي المكتبة ينتظرون أكثر من مجرد إشعارات بريدية، فكثير منهم يريدون رسائل نصية قصيرة (SMS) عن المشاكل الحرجة التي لا تحتمل الانتظار، ويود غيرهم أن يُنبَّهوا على فيس بوك، وكذلك بالمثل للموظفين الذين يرغبون في تلقي رسائل على سلاك (Slack). انظر (ش.2)
لهذا توسِّع فئة Notifier
وتضع أساليب تنبيه إضافية في فئات فرعية، ويفترض بالعميل أن يعرف فئة التنبيه المطلوبة ويستخدمها لكل الإشعارات التالية، وستدرك أنك ستحتاج في الحالات الحرجة إلى إشعار المستخدم في كل القنوات التي هو فيها -فيس بوك، بريد، سلاك، ...- والطريقة المباشرة لفعل ذلك هي إنشاء فئات فرعية خاصة تجمع عدة أساليب إشعارات في فئة واحدة، لكن عيبها أن الشيفرة ستصبح ضخمة، سواء شيفرة المكتبة أو العميل، ونريد أن نجد طريقة أفضل لهيكلة فئات الإشعارات بحيث لا تكبر أعدادها بشكل زائد عن الحد. انظر (ش.3)
الحل
رغم أن توسعة الفئة هي أول ما يرد للذهن عند تغيير سلوك كائن ما إلا أن الاكتساب (Inheritance) له مؤشرات خطر يجب أن تدركها:
- الاكتساب ساكن، فلا يمكنك تغيير سلوك كائن موجود فعلًا في وقت التشغيل (run time) ولا يمكنك فعل شيء سوى استبدال الكائن بكامله بواحد أنشاته من فئة فرعية.
- لا يمكن أن يكون للفئات الفرعية سوى فئة أم وحيدة، ذلك أن الاكتساب لا يسمح في أغلب اللغات للفئة أن تكتسب سلوكيات من عدة فئات في نفس الوقت.
وإحدى الطرائق التي تتغلب بها على أوجه القصور تلك هي استخدام التركيب (Composition) بدلًا من الاكتساب، فالتركيب يجعل للكائن مرجعًا إلى كائن آخر يفوض إليه بعض المهام، في حين أنه مع الاكتساب يكون الكائن نفسه قادرًا على تنفيذ تلك المهام مكتسبًا السلوك من الفئة الأم. تستطيع كذلك استبدال كائن المساعد (Helper) بغيره، لتغير بذلك سلوك الحاوية أثناء وقت التشغيل، ويستطيع الكائن استخدام السلوك من فئات مختلفة ويحمل مراجع إلى عدة كائنات يفوض إليها المهام. ويُعد التركيب هو المبدأ الأساسي خلف عدة أنماط تصميم بما فيها المزخرِف. انظر (ش.4)
ولنمط المزخرف اسم آخر يصف وظيفة النمط بوضوح، وهو "المغلِّف"، إذ أن المغلِّف هو كائن يمكن ربطه بكائن "هدف"، ويحتوي المغلِّف على نفس الأساليب التي في الكائن الهدف، ويفوِّض إليه كل الطلبات التي يستلمها، لكن قد يغير المغلِّف النتيجة بشيء ما قبل أو بعد تمريره الطلب إلى الهدف.
يستخدم المغلِّف نفس الواجهة كالكائن الذي يتم تغليفه (المغلَّف)، لهذا تبدو تلك الكائنات متطابقة من وجهة نظر العميل، ولكي تغطي كائنًا ما بعدة مغلِّفات مضيفًا سلوكًا مجمعًا من كل المغلِّفات إليه، يجب أن تجعل الحقل المرجعي للمغلِّف يقبل أي كائن يتبع تلك الواجهة. وفي مثال الإشعارات الذي تقدم سنترك سلوك إشعارات البريد البسيط داخل فئة Notifier
الأساسية، لكن سنحول كل أساليب الإشعار الأخرى إلى مزخرفات (Decorators). انظر (ش.5)
ستحتاج شيفرة العميل أن تغلف كائن إشعار بسيط بمجموعة مزخرفات تطابق تفضيلات العميل، وستهيكَل الكائنات في هيئة مكدَّس (Stack). انظر (ش.6)
وسيكون آخر مزخرِف في المكدس هو الكائن الذي يعمل معه العميل، وبما أن كل المزخرِفات تستخدم نفس الواجهة التي يستخدمها المنبه الأساسي (Base Notifier)، فإن شيفرة العميل لن يهمها إن كانت تعمل مع كائن منبه "نقي" أو مزخرَف، ونستطيع تطبيق نفس المنظور على سلوكيات أخرى مثل تنسيق الرسائل أو تنظيم قائمة المستلمين، ويستطيع العميل زخرفة الكائن بأي مزخرِفات خاصة طالما أنها تتبع نفس الواجهة كالباقين.
مثال واقعي
ارتداء الملابس مثال على استخدام المزخرِفات، فأنت تغلف نفسك بكنزة مثلًا طلبًا للدفء في البرد اليسير، ثم تضيف فوقها معطفًا إن اشتد البرد، أما إن كانت تمطر فستلبس معطفًا خاصًا بالمطر، وكل تلك القطع توسع سلوكك الأساسي لكنها ليست عنصرًا منك، وتستطيع خلع أي قطعة ملابس في أي وقت تشعر فيه أنك لم تحتاج إليها.
البُنية
- يصرح العنصر (Component) عن واجهة مشتركة لكل من المغلِّفات والكائنات المغلَّفة.
- العنصر الحقيقي (Concrete Component) هو فئة الكائنات يتم تغليفها، وتعرِّف السلوك الأساسي الذي يمكن تغييره بواسطة المزخرِفات.
- فئة المزخرف الأساسي (Base Decorator) لها حقل للإشارة إلى كائن مغلَّف، ويجب أن يصرَّح عن نوع الحقل على أنه واجهة العنصر كي يستطيع احتواء كلًا من العناصر الحقيقية والمزخرِفات. ويفوِّض المزخرِف الأساسي العمليات كلها إلى الكائن المغلَّف.
- تعرِّف المزخرِفات الحقيقية (Concrete Decorators) سلوكيات إضافية يمكن أن تضاف إلى العناصر بديناميكية، وتتخطى المزخرِفات الحقيقية أساليب المزخرِف الأساسي وتنفذ أسلوبها قبل أو بعد استدعاء الأسلوب الأم (Parent Behavior).
- العميل (Client) يستطيع تغليف العناصر في طبقات متعددة من المزخرِفات طالما أنها تعمل مع كل الكائنات من خلال واجهة العنصر (Component Interface).
مثال توضيحي
في هذا المثال يسمح لك نمط المزخرِف بضغط وتشفير البيانات الحساسة بشكل مستقل عن الشيفرة التي تستخدم تلك البيانات. انظر (ش.8)
يغلِّف التطبيق كائن مصدرِ البيانات بزوج من المزخرِفات، وكلا المزخرفين يغيران الطريقة التي تُكتب البيانات بها وتقرأ من القرص:
- تشفر المزخرِفات البيانات وتضغطها قبل كتابتها على القرص مباشرة، وتكتب الفئةُ الأساسيةُ البيانات المحمية والمشفرة إلى الملف دون العلم بالتغيير.
- تمر البيانات بعد قراءتها من القرص مباشرة على نفس المزخرِفات التي ستفك ضغطها وتفك تشفيرها.
تستخدم المزخرِفات وفئةُ مصدرِ البيانات نفس الواجهة التي تجعلها كلها قابلة للتعديل بشكل متبادل (interchangeable) في شيفرة العميل.
// تعرِّف واجهةُ العنصر العملياتَ التي يمكن تغييرها بواسطة المزخرفات.
interface DataSource is
method writeData(data)
method readData():data
// تضيف العناصر الحقيقية استخدامات افتراضية للعمليات، قد تكون هناك صور مختلفة لهذه الفئات
// في برنامج ما
class FileDataSource implements DataSource is
constructor FileDataSource(filename) { ... }
method writeData(data) is
// اكتب البيانات إلى الملف.
method readData():data is
// اقرأ البيانات من الملف.
// تتبع فئة المزخرِف الأساسي نفس الواجهة كالعناصر الأخرى، والقرص الأساسي لهذه الفئة هو تعريف واجهة التغليف
// لكل المزخرِفات الحقيقية. قد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين العنصر المغلَّف
// ووسائل بدئها.
class DataSourceDecorator implements DataSource is
protected field wrappee: DataSource
constructor DataSourceDecorator(source: DataSource) is
wrappee = source
// يفوِّض المزخرِف الأساسي كل المهام إلى العنصر المغلَّف، ويمكن إضافة السلوكيات الإضافية من المزخرِفات
// الحقيقية.
method writeData(data) is
wrappee.writeData(data)
// قد تستدعي المزخرِفات الاستخدام الأم للعملية بدلًا من استدعاء
// الكائن المغلَّف مباشرة، وهذا المنظور يبسط توسعة فئات المزخرِف.
method readData():data is
return wrappee.readData()
// يجب أن تستدعي المزخرِفات الحقيقيةُ الأساليبَ على الكائن المغلَّف،
// لكن قد تضيف من عندها شيئًا إلى النتيجة
// تستطيع المزخرِفات تنفيذ السلوك المضاف قبل أو بعد استدعاء الكائن
// المغلَّف.
class EncryptionDecorator extends DataSourceDecorator is
method writeData(data) is
// 1. شفِّر البيانات المُمَرَّرة.
// 2. للكائن المغلَّف writeData مرر البيانات المشفرة إلى أسلوب
method readData():data is
// 1. للكائن المغلَّف readData اجلب البيانات من أسلوب.
// 2. حاول فك تشفيرها إن كانت مشفرة.
// 3. أعد النتيجة.
// يمكنك تغليف الكائنات في عدة طبقات من المزخرِفات.
class CompressionDecorator extends DataSourceDecorator is
method writeData(data) is
// 1. اضغط البيانات المُمَرَّرة.
// 2. للكائن writeData مرر البيانات المضغوطة إلى أسلوب
// المغلَّف.
method readData():data is
// 1. للكائن المغلَّف readData اجلب البيانات من أسلوب.
// 2. حاول فك ضغطها إن كانت مضغوطة.
// 3. أعد النتيجة.
// خيار1. مثال بسيط لتجميع المزخرف.
class Application is
method dumbUsageExample() is
source = new FileDataSource("somefile.dat")
source.writeData(salaryRecords)
// كُتب الملف الهدف ببيانات غير مشفرة.
source = new CompressionDecorator(source)
source.writeData(salaryRecords)
// كُتب الملف الهدف ببيانات مشفرة.
source = new EncryptionDecorator(source)
// يحتوي متغير المصدر الآن على هذا:
// Encryption > Compression > FileDataSource
source.writeData(salaryRecords)
// كُتب الملف الهدف ببيانات مشفرة ومضغوطة.
// خيار 2. شيفرة العميل التي تستخدم مصدر بيانات خارجي.
// أو تهتم بتفاصيل تخزين البيانات، إذ تعمل مع مصدر بيانات معد مسبقًا SalaryManager لا تعرف كائنات
// (app configurator) تم استقباله من قبل مهيئ التطبيق.
class SalaryManager is
field source: DataSource
constructor SalaryManager(source: DataSource) { ... }
method load() is
return source.readData()
method save() is
source.writeData(salaryRecords)
// ...أساليب أخرى مفيدة...
// يستطيع التطبيق أن يجمع تكديسات مختلفة من المزخرِفات أثناء وقت التشغيل اعتمادًا على الإعدادات أو البيئة.
class ApplicationConfigurator is
method configurationExample() is
source = new FileDataSource("salary.dat")
if (enabledEncryption)
source = new EncryptionDecorator(source)
if (enabledCompression)
source = new CompressionDecorator(source)
logger = new SalaryManager(source)
salary = logger.load()
// ...
قابلية الاستخدام
- استخدم نمط المزخرف عند الحاجة لتعيين سلوكيات إضافية إلى الكائنات في وقت التشغيل دون تعطيل الشيفرة التي تستخدم تلك الكائنات.
يسمح لك نمط المزخرف بهيكلة منطق تجارتك (Business Logic) في هيئة طبقات، فأنشئ مزخرِفًا لكل طبقة ونظم الكائنات مع تجميعات مختلفة من هذا المنطق في وقت التشغيل. تستطيع شيفرة العميل أن تعامل كل تلك الكائنات بنفس الطريقة بما أنها جميعًا تتبع نفس الواجهة المشتركة.
- استخدم نمط المزخرِف عند عدم إمكانية توسعة سلوك الكائن باستخدام الاكتساب (Inheritance).
ستجد كلمة final المفتاحية في أغلب لغات البرمجة، وتستخدم لمنع التوسعات المستقبلية لفئة ما، والطريقة الوحيدة لإعادة استخدام السلوك الموجود فعليًا في فئة نهائية (final) هو بتغليف الفئة بمُغلِّفك الخاص باستخدام نمط المزخرِف.
كيفية الاستخدام
- تأكد أن نطاقك التجاري يمكن تمثيله كعنصر أساسي مع عدة طبقات اختيارية فوقه.
- اكتشف أي الأساليب التي تشترك فيها الطبقات الخيارية مع العنصر الأساسي، ومن ثم أنشئ واجهة للعنصر وصرح عن تلك الأساليب هناك.
- أنشئ فئة عنصر حقيقي وعرَّف السلوك الأساسي فيها.
- أنشئ فئة مزخرِف أساسي، وينبغي أن يكون فيها حقل لتخزين مرجع إلى الكائن المُغلَّف، ويجب أن يُصرَّح عن الحقل مع نوع واجهة العنصر (Component Interface Type) ليسمح بالربط مع العناصر الحقيقية والمزخرِفات، ويجب أن يفوض المزخرِف الأساسي كل المهام إلى الكائن المغلَّف.
- تأكد أن كل الفئات تستخدم واجهة العنصر.
- أنشئ مزخرَفات حقيقية بتوسيعها من المزخرِف الأساسي، ويجب أن ينفذ المزخرف الحقيقي سلوكه قبل أو بعد الاستدعاء إلى الأسلوب الأم (Parent Method)، والذي يفوض بدوره المهام إلى الكائن المغلَّف.
- يجب أن تكون شيفرة العميل مسؤولة عن إنشاء المزخرِفات وترتيبها بالطريقة التي يحتاجها العميل.
المزايا والعيوب
المزايا
- توسيع سلوك الكائن دون إنشاء فئة فرعية جديدة.
- إضافة أو حذف مسؤوليات من الكائن أثناء التشغيل.
- جمع عدة سلوكيات من خلال تغليف كائن بعدة مزخرِفات.
- مبدأ المسؤولية الواحدة. يمكنك تقسيم فئة أحادية تستخدم عدة صور محتملة من السلوكيات إلى عدة فئات أصغر منها.
العيوب
- صعوبة حذف مغلِّف ما من المكدِّسات (Stacks).
- صعوبة استخدام مزخرِف بحيث لا يعتمد سلوكه على الترتيب داخل مكدِّس المزخرِفات.
- قد تبدو شيفرة الإعدادات الأولية قبيحة في البداية.
العلاقات مع الأنماط الأخرى
- يغير نمط المحوِّل واجهة كائن موجود فعلًا بينما يحسِّن المزخرِفُ الكائنَ دون تغيير واجهته، وإضافة لهذا فإن المزخرف يدعم التركيب التكراري (Recursive Composition) الذي يستحيل عند استخدام المحول.
- يقدم المحول واجهة مختلفة للكائن المغلَّف، ويقدم نمط الوكيل (Proxy) نفس الواجهة له، أما المزخرِف فيقدم واجهة محسَّنة.
- يتشابه نمطيْ المزخرِف وسلسلة المسؤوليات في هيكلة الفئات إذ يعتمد كلا النمطين على التركيب التكراري لتمرير التنفيذ خلال سلسلة كائنات، لكن بأي حال هناك فروق كبيرة أخرى بينهما، فسلسلة المسؤوليات تستطيع تنفيذ عمليات عشوائية بشكل مستقل عن بعضها، كما تستطيع إيقاف تمرير الطلب عند أي نقطة لما بعدها، ومن الناحية الأخرى تستطيع مزخرفات عديدة توسيع سلوك الكائن مع الحفاظ عليه متناسقًا مع الواجهة الأساسية، وإضافة لهذا فلا يُسمح للمزخرِفات أن تعطل سير الطلب.
- للمركَّب والمزخرِف مخططات هيكلية متشابهة بما أن كليهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية من الكائنات. ويشبه المزخرِفُ المركَّبَ لكنه لا يملك إلا عنصرًا فرعيًا واحدًا، كما أن المزخرف يضيف مسؤوليات إضافية إلى الكائن المغلَّف في حين أن المركب لا يزيد على جمع النتائج من فروعه. لكن هذين النمطين قد يتعاونان فيما بينهما إذ يمكنك استخدام المزخرف لتوسيع سلوك كائن بعينه في شجرة المركَّب.
- التصميمات التي تستخدم المركب والمزخرف بكثرة تستفيد من استخدام النموذج الأولي، ذلك أن استخدامه يسمح لك بنسخ هياكل معقدة بدلًا من إعادة إنشائهم من الصفر.
- يسمح المزخرف بتغيير مظهر كائن ما، بينما يسمح لك نمط الخطة بتغيير جوهره.
- رغم تشابه المزخرف والوكيل في الهيكلة إلا أنهما يختلفان تمامًا في الهدف منهما، فالنمطين كلاهما قد بنيا على مبدأ التركيب حيث يفترض بكائن ما أن يفوض بعضًا من مهامه إلى غيره، لكن الفرق هو أن الوكيل عادة ما يدير دورة حياة كائن الخدمة الخاصة به بنفسه، بينما يتحكم العميل دومًا في تركيب المزخرفات.
الاستخدام في لغة جافا
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). إليك بعض الأمثلة على المزخرِف في مكتبات جافا:
- كل الفئات الفرعية لـ java.io.InputStream ، OutputStream ، Reader ، Writer لديها منشئات تقبل كائنات من نفس نوعها.
- أساليب java.util.Collections و ()checkedXXX و ()synchronizedXXX و ()unmodifiableXXX
- javax.servlet.http.HttpServletRequestWrapper و
HttpServletResponseWrapper
.
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال: مزخرفات التشفير والضغط
يوضح المثال كيفية تعديل سلوك كائن ما دون تغيير شيفرته، ففي البداية كان المنطق التجاري (Business Logic) يستطيع قراءة وكتابة البيانات في صورة نص بسيط (Plain text)، ثم أنشأنا عدة فئات مغلِّفات صغيرة تضيف سلوكًا جديدًا بعد تنفيذ العمليات القياسية في كائن مغلَّف. وأول مغلِّف يشفر البيانات ويفك تشفيرها، أما الثاني فيضغطها ويفك ضغطها، وتستطيع جمع أو دمج تلك المغلِّفات من خلال تغليف مزخرف داخل آخر.
المزخرفات
decorators/DataSource.java: واجهة بيانات مشتركة، تعرِّف عمليات القراءة والكتابة
package refactoring_guru.decorator.example.decorators;
public interface DataSource {
void writeData(String data);
String readData();
}
decorators/FileDataSource.java: قارئ/كاتب بيانات بسيط
package refactoring_guru.decorator.example.decorators;
import java.io.*;
public class FileDataSource implements DataSource {
private String name;
public FileDataSource(String name) {
this.name = name;
}
@Override
public void writeData(String data) {
File file = new File(name);
try (OutputStream fos = new FileOutputStream(file)) {
fos.write(data.getBytes(), 0, data.length());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}
@Override
public String readData() {
char[] buffer = null;
File file = new File(name);
try (FileReader reader = new FileReader(file)) {
buffer = new char[(int) file.length()];
reader.read(buffer);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
return new String(buffer);
}
}
decorators/DataSourceDecorator.java: مزخرف أساسي مجرد
package refactoring_guru.decorator.example.decorators;
public class DataSourceDecorator implements DataSource {
private DataSource wrappee;
DataSourceDecorator(DataSource source) {
this.wrappee = source;
}
@Override
public void writeData(String data) {
wrappee.writeData(data);
}
@Override
public String readData() {
return wrappee.readData();
}
}
decorators/EncryptionDecorator.java: مزخرف التشفير
package refactoring_guru.decorator.example.decorators;
import java.util.Base64;
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) {
super(source);
}
@Override
public void writeData(String data) {
super.writeData(encode(data));
}
@Override
public String readData() {
return decode(super.readData());
}
private String encode(String data) {
byte[] result = data.getBytes();
for (int i = 0; i < result.length; i++) {
result[i] += (byte) 1;
}
return Base64.getEncoder().encodeToString(result);
}
private String decode(String data) {
byte[] result = Base64.getDecoder().decode(data);
for (int i = 0; i < result.length; i++) {
result[i] -= (byte) 1;
}
return new String(result);
}
}
decorators/CompressionDecorator.java: مزخرف الضغط
package refactoring_guru.decorator.example.decorators;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
public class CompressionDecorator extends DataSourceDecorator {
private int compLevel = 6;
public CompressionDecorator(DataSource source) {
super(source);
}
public int getCompressionLevel() {
return compLevel;
}
public void setCompressionLevel(int value) {
compLevel = value;
}
@Override
public void writeData(String data) {
super.writeData(compress(data));
}
@Override
public String readData() {
return decompress(super.readData());
}
private String compress(String stringData) {
byte[] data = stringData.getBytes();
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
DeflaterOutputStream dos = new DeflaterOutputStream(bout, new Deflater(compLevel));
dos.write(data);
dos.close();
bout.close();
return Base64.getEncoder().encodeToString(bout.toByteArray());
} catch (IOException ex) {
return null;
}
}
private String decompress(String stringData) {
byte[] data = Base64.getDecoder().decode(stringData);
try {
InputStream in = new ByteArrayInputStream(data);
InflaterInputStream iin = new InflaterInputStream(in);
ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
int b;
while ((b = iin.read()) != -1) {
bout.write(b);
}
in.close();
iin.close();
bout.close();
return new String(bout.toByteArray());
} catch (IOException ex) {
return null;
}
}
}
Demo.java: شيفرة العميل
package refactoring_guru.decorator.example;
import refactoring_guru.decorator.example.decorators.*;
public class Demo {
public static void main(String[] args) {
String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
DataSourceDecorator encoded = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("out/OutputDemo.txt")));
encoded.writeData(salaryRecords);
DataSource plain = new FileDataSource("out/OutputDemo.txt");
System.out.println("- Input ----------------");
System.out.println(salaryRecords);
System.out.println("- Encoded --------------");
System.out.println(plain.readData());
System.out.println("- Decoded --------------");
System.out.println(encoded.readData());
}
}
OutputDemo.txt: نتائج التنفيذ
- Input ----------------
Name,Salary
John Smith,100000
Steven Jobs,912000
- Encoded --------------
Zkt7e1Q5eU8yUm1Qe0ZsdHJ2VXp6dDBKVnhrUHtUe0sxRUYxQkJIdjVLTVZ0dVI5Q2IwOXFISmVUMU5rcENCQmdxRlByaD4+
- Decoded --------------
Name,Salary
John Smith,100000
Steven Jobs,912000
الاستخدام في لغة #C
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
namespace RefactoringGuru.DesignPatterns.Composite.Conceptual
{
// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي.
public abstract class Component
{
public abstract string Operation();
}
// تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
class ConcreteComponent : Component
{
public override string Operation()
{
return "ConcreteComponent";
}
}
// تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف
// وكذلك أساليب استدعائه.
abstract class Decorator : Component
{
protected Component _component;
public Decorator(Component component)
{
this._component = component;
}
public void SetComponent(Component component)
{
this._component = component;
}
// يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
public override string Operation()
{
if (this._component != null)
{
return this._component.Operation();
}
else
{
return string.Empty;
}
}
}
// تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(Component comp) : base(comp)
{
}
// للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
// الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
public override string Operation()
{
return $"ConcreteDecoratorA({base.Operation()})";
}
}
// تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(Component comp) : base(comp)
{
}
public override string Operation()
{
return $"ConcreteDecoratorB({base.Operation()})";
}
}
public class Client
{
// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية
// للعناصر التي تعمل معها.
public void ClientCode(Component component)
{
Console.WriteLine("RESULT: " + component.Operation());
}
}
class Program
{
static void Main(string[] args)
{
Client client = new Client();
var simple = new ConcreteComponent();
Console.WriteLine("Client: I get a simple component:");
client.ClientCode(simple);
Console.WriteLine();
// ...إضافة إلى العناصر المزخرفة أيضًا.
//
// لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات
// الأخرى كذلك.
ConcreteDecoratorA decorator1 = new ConcreteDecoratorA(simple);
ConcreteDecoratorB decorator2 = new ConcreteDecoratorB(decorator1);
Console.WriteLine("Client: Now I've got a decorated component:");
client.ClientCode(decorator2);
}
}
}
Output.txt: نتائج التنفيذ
Client: I get a simple component:
RESULT: ConcreteComponent
Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
الاستخدام في لغة PHP
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Decorator\Conceptual;
/**
* العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي.
*/
interface Component
{
public function operation(): string;
}
/**
* تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
*/
class ConcreteComponent implements Component
{
public function operation(): string
{
return "ConcreteComponent";
}
}
/**
* تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
* التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف
* وكذلك أساليب استدعائه.
*/
class Decorator implements Component
{
/**
* @var Component
*/
protected $component;
public function __construct(Component $component)
{
$this->component = $component;
}
/**
* يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
*/
public function operation(): string
{
return $this->component->operation();
}
}
/**
* تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
*/
class ConcreteDecoratorA extends Decorator
{
/**
* للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
* الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
*/
public function operation(): string
{
return "ConcreteDecoratorA(" . parent::operation() . ")";
}
}
/**
* تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
*/
class ConcreteDecoratorB extends Decorator
{
public function operation(): string
{
return "ConcreteDecoratorB(" . parent::operation() . ")";
}
}
/**
* تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية
* للعناصر التي تعمل معها.
*/
function clientCode(Component $component)
{
// ...
echo "RESULT: " . $component->operation();
// ...
}
/**
* وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة...
*/
$simple = new ConcreteComponent;
echo "Client: I've got a simple component:\n";
clientCode($simple);
echo "\n\n";
/**
* ...إضافة إلى العناصر المزخرَفة.
*
* لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرِفات الأخرى كذلك.
*/
$decorator1 = new ConcreteDecoratorA($simple);
$decorator2 = new ConcreteDecoratorB($decorator1);
echo "Client: Now I've got a decorated component:\n";
clientCode($decorator2);
Output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: ConcreteComponent
Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
مثال واقعي
في هذا المثال يساعدك نمط المزخرِف على إنشاء قواعد ترشيح نصوص معقدة من أجل تنقية المحتوى قبل نشره على صفحة ويب، ولأن أنواع المحتوى المختلفة مجموعات مختلفة من المرشحات، فإنها تتطلب مجموعات مختلفة من المرشحات. فقد ترغب مثلًا في إزالة شيفرة HTML من التعليقات لكن ترغب في نفس الوقت بإبقاء وسوم بسيطة في منشورات المنتديات.
وقد ترغب في السماح بنشر بصيغة Markdown التي ستعالَج قبل أي ترشيح يحدث في HTML، وكل هذه القواعد في الترشيحات يمكن تمثيلها كفئات مزخرِفات منفصلة تكدس بأشكال مختلفة وفقًا لطبيعة المحتوى لديك.
index.php: مثال واقعي
<?php
namespace RefactoringGuru\Decorator\RealWorld;
/**
* تصرِّح واجهة العنصر عن أسلوب ترشيح يجب استخدامه من قِبل جميع العناصر الحقيقية
* والمزخرِفات.
*/
interface InputFormat
{
public function formatText(string $text): string;
}
/**
* العنصر الحقيقي عنصر أساسي في الزخرفة إذ يحتوي على النص الأصلي كما هو دون
* ترشيح أو تنسيق.
*/
class TextInput implements InputFormat
{
public function formatText(string $text): string
{
return $text;
}
}
/**
* لا تحتوي فئة المزخرف الأساسية على أي ترشيح حقيقي أو منطق تنسيق، ووظيفتها الأساسية هي
* تنفيذ بنية الزخرفة التحتية الأساسية:
* حقل لتخزين عنصر مغلَّف أو مزخرِف آخر، وأسلوب التنسيق الأساسي الذي يفوض العمل إلى الكائن
* المغلَّف.
* تنفَّذ مهمة التنسيق الحقيقية بواسطة الفئات الفرعية.
*/
class TextFormat implements InputFormat
{
/**
* @var InputFormat
*/
protected $inputFormat;
public function __construct(InputFormat $inputFormat)
{
$this->inputFormat = $inputFormat;
}
/**
* يفوض المزخرِف المهام الأساسية إلى العنصر المغلَّف.
*/
public function formatText(string $text): string
{
return $this->inputFormat->formatText($text);
}
}
/**
* HTML هذا المزخرِف الحقيقي يجرد النص من كل وسوم.
*/
class PlainTextFilter extends TextFormat
{
public function formatText(string $text): string
{
$text = parent::formatText($text);
return strip_tags($text);
}
}
/**
* التي قد (Attributes) الخطرة فقط، والوسائط HTML هذا المزخرف الحقيقي يجرد وسوم \
* XSS تسبب ثغرات.
*/
class DangerousHTMLTagsFilter extends TextFormat
{
private $dangerousTagPatterns = [
"|<script.*?>([\s\S]*)?</script>|i", // ...
];
private $dangerousAttributes = [
"onclick", "onkeypress", // ...
];
public function formatText(string $text): string
{
$text = parent::formatText($text);
foreach ($this->dangerousTagPatterns as $pattern) {
$text = preg_replace($pattern, '', $text);
}
foreach ($this->dangerousAttributes as $attribute) {
$text = preg_replace_callback('|<(.*?)>|', function ($matches) use ($attribute) {
$result = preg_replace("|$attribute=|i", '', $matches[1]);
return "<" . $result . ">";
}, $text);
}
return $text;
}
}
/**
* HTML إلى Markdown هذا المزخرف الحقيقي يضيف تحويلًا أوليًا من.
*/
class MarkdownFormat extends TextFormat
{
public function formatText(string $text): string
{
$text = parent::formatText($text);
// (Block Elements) نسِّق عناصر المتن.
$chunks = preg_split('|\n\n|', $text);
foreach ($chunks as &$chunk) {
// Format headers.
if (preg_match('|^#+|', $chunk)) {
$chunk = preg_replace_callback('|^(#+)(.*?)$|', function ($matches) {
$h = strlen($matches[1]);
return "<h$h>" . trim($matches[2]) . "</h$h>";
}, $chunk);
} // نسِّق الفقرات.
else {
$chunk = "<p>$chunk</p>";
}
}
$text = implode("\n\n", $chunks);
// (inline) نسِّق العناصر السطرية.
$text = preg_replace("|__(.*?)__|", '<strong>$1</strong>', $text);
$text = preg_replace("|\*\*(.*?)\*\*|", '<strong>$1</strong>', $text);
$text = preg_replace("|_(.*?)_|", '<em>$1</em>', $text);
$text = preg_replace("|\*(.*?)\*|", '<em>$1</em>', $text);
return $text;
}
}
/**
* قد تكون شيفرة العميل عنصرًا من موقع حقيقي يخرج محتوىً ينتجه المستخدمون، فلا يعنيها
* إن كانت تحصل على كائن عنصر بسيط أو مزخرَف يما أنها تعمل مع المنسِّقات من خلال واجهة
* العنصر .
*/
function displayCommentAsAWebsite(InputFormat $format, string $text)
{
// ..
echo $format->formatText($text);
// ..
}
/**
* منسقات الإدخال مفيدة عند التعامل مع المحتوى الذي ينتجه المستخدمون، فقد يكون عرض ذلك
* المحتوى كما هو خطيرًا للغاية، خاصة في حالة إنتاجه من قبل مستخدمين مجهولين كما في
* XSS حالة التعليقات، ذلك أن موقعك حينها يكون معرضًا لهجمات السخام وهجمات
*/
$dangerousComment = <<<HERE
Hello! Nice blog post!
Please visit my <a href='http://www.iwillhackyou.com'>homepage</a>.
<script src="http://www.iwillhackyou.com/script.js">
performXSSAttack();
</script>
HERE;
/**
* (معالجة بسيطة للتعليقات (غير آمنة.
*/
$naiveInput = new TextInput;
echo "Website renders comments without filtering (unsafe):\n";
displayCommentAsAWebsite($naiveInput, $dangerousComment);
echo "\n\n\n";
/**
* (معالجة مرشَّحة للتعليقات (آمنة.
*/
$filteredInput = new PlainTextFilter($naiveInput);
echo "Website renders comments after stripping all tags (safe):\n";
displayCommentAsAWebsite($filteredInput, $dangerousComment);
echo "\n\n\n";
/**
* يسمح المزخرف بتكديس عدة صيغ إدخال للحصول على تحكم دقيق على المحتوى المعالَج.
*/
$dangerousForumPost = <<<HERE
# Welcome
This is my first post on this **gorgeous** forum.
<script src="http://www.iwillhackyou.com/script.js">
performXSSAttack();
</script>
HERE;
/**
* (معالجة بسيطة للمنشور (غير آمنة، وبدون تنسيق.
*/
$naiveInput = new TextInput;
echo "Website renders a forum post without filtering and formatting (unsafe, ugly):\n";
displayCommentAsAWebsite($naiveInput, $dangerousForumPost);
echo "\n\n\n";
/**
* (ترشيح الوسوم الخطرة (آمن، حسن المظهر + Markdown منسِّق .
*/
$text = new TextInput;
$markdown = new MarkdownFormat($text);
$filteredInput = new DangerousHTMLTagsFilter($markdown);
echo "Website renders a forum post after translating markdown markup" .
"and filtering some dangerous HTML tags and attributes (safe, pretty):\n";
displayCommentAsAWebsite($filteredInput, $dangerousForumPost);
echo "\n\n\n";
Output.txt: نتائج التنفيذ
Website renders comments without filtering (unsafe):
Hello! Nice blog post!
Please visit my <a href='http://www.iwillhackyou.com'>homepage</a>.
<script src="http://www.iwillhackyou.com/script.js">
performXSSAttack();
</script>
Website renders comments after stripping all tags (safe):
Hello! Nice blog post!
Please visit my homepage.
performXSSAttack();
Website renders a forum post without filtering and formatting (unsafe, ugly):
# Welcome
This is my first post on this **gorgeous** forum.
<script src="http://www.iwillhackyou.com/script.js">
performXSSAttack();
</script>
Website renders a forum post after translating markdown markupand filtering some dangerous HTML tags and attributes (safe, pretty):
<h1>Welcome</h1>
<p>This is my first post on this <strong>gorgeous</strong> forum.</p>
<p></p>
الاستخدام في لغة بايثون
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة بايثون، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
class Component():
"""
العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي.
"""
def operation(self) -> str:
pass
class ConcreteComponent(Component):
"""
تقدم المكونات الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
"""
def operation(self) -> str:
return "ConcreteComponent"
class Decorator(Component):
"""
تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف
وكذلك أساليب استدعائه.
"""
_component: Component = None
def __init__(self, component: Component) -> None:
self._component = component
@property
def component(self) -> str:
"""
يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
"""
return self._component
def operation(self) -> str:
self._component.operation()
class ConcreteDecoratorA(Decorator):
"""
تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
"""
def operation(self) -> str:
"""
للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
"""
return f"ConcreteDecoratorA({self.component.operation()})"
class ConcreteDecoratorB(Decorator):
"""
تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
"""
def operation(self) -> str:
return f"ConcreteDecoratorB({self.component.operation()})"
def client_code(component: Component) -> None:
"""
تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية.
"""
# ...
print(f"RESULT: {component.operation()}", end="")
# ...
if __name__ == "__main__":
# وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة...
simple = ConcreteComponent()
print("Client: I've got a simple component:")
client_code(simple)
print("\n")
# ...إضافة إلى العناصر المزخرفة أيضًا.
#
# لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك.
decorator1 = ConcreteDecoratorA(simple)
decorator2 = ConcreteDecoratorB(decorator1)
print("Client: Now I've got a decorated component:")
client_code(decorator2)
Output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: ConcreteComponent
Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
الاستخدام في لغة روبي
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تحدد واجهة العنصر الأساسي.
class Component
# @return [String]
def operation
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
class ConcreteComponent < Component
# @return [String]
def operation
'ConcreteComponent'
end
end
# تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه
# الفئة هو تعريف واجهة التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام
# الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف وكذلك أساليب استدعائه.
class Decorator < Component
attr_accessor :component
# @param [Component] component
def initialize(component)
@component = component
end
# يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
def operation
@component.operation
end
end
# تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
class ConcreteDecoratorA < Decorator
# للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
# الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
def operation
"ConcreteDecoratorA(#{@component.operation})"
end
end
# تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
class ConcreteDecoratorB < Decorator
# @return [String]
def operation
"ConcreteDecoratorB(#{@component.operation})"
end
end
# تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر، وهكذا
# تظل مستقلة عن الفئات الحقيقية.
def client_code(component)
# ...
print "RESULT: #{component.operation}"
# ...
end
# وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة...
simple = ConcreteComponent.new
puts 'Client: I\'ve got a simple component:'
client_code(simple)
puts "\n\n"
# ...إضافة إلى العناصر المزخرفة أيضًا.
#
# لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك.
decorator1 = ConcreteDecoratorA.new(simple)
decorator2 = ConcreteDecoratorB.new(decorator1)
puts 'Client: Now I\'ve got a decorated component:'
client_code(decorator2)
output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: ConcreteComponent
Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
الاستخدام في لغة Swift
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Example.swift: مثال تصوري
import XCTest
/// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي.
protocol Component {
func operation() -> String
}
/// تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
class ConcreteComponent: Component {
func operation() -> String {
return "ConcreteComponent"
}
}
/// تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
/// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين عنصر مغلَّف
/// وكذلك أساليب استدعائه.
class Decorator: Component {
private var component: Component
init(_ component: Component) {
self.component = component
}
/// يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
func operation() -> String {
return component.operation()
}
}
/// تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
class ConcreteDecoratorA: Decorator {
/// للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
/// الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
override func operation() -> String {
return "ConcreteDecoratorA(" + super.operation() + ")"
}
}
/// تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
class ConcreteDecoratorB: Decorator {
override func operation() -> String {
return "ConcreteDecoratorB(" + super.operation() + ")"
}
}
/// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية
/// للعناصر التي تعمل معها.
class Client {
// ...
static func someClientCode(component: Component) {
print("Result: " + component.operation())
}
// ...
}
/// دعنا نرى كيف ستعمل كل تلك العناصر معًا.
class DecoratorConceptual: XCTestCase {
func testDecoratorStructure() {
// وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة...
print("Client: I've got a simple component")
let simple = ConcreteComponent()
Client.someClientCode(component: simple)
// ...إضافة إلى العناصر المزخرفة أيضًا
//
// لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرفات الأخرى كذلك.
let decorator1 = ConcreteDecoratorA(simple)
let decorator2 = ConcreteDecoratorB(decorator1)
print("\nClient: Now I've got a decorated component")
Client.someClientCode(component: decorator2)
}
}
Output.txt: نتائج التنفيذ
Client: I've got a simple component
Result: ConcreteComponent
Client: Now I've got a decorated component
Result: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
مثال واقعي
Example.swift: شيفرة العميل
import UIKit
import XCTest
protocol ImageEditor: CustomStringConvertible {
func apply() -> UIImage
}
class ImageDecorator: ImageEditor {
private var editor: ImageEditor
required init(_ editor: ImageEditor) {
self.editor = editor
}
func apply() -> UIImage {
print(editor.description + " applies changes")
return editor.apply()
}
var description: String {
return "ImageDecorator"
}
}
extension UIImage: ImageEditor {
func apply() -> UIImage {
return self
}
open override var description: String {
return "Image"
}
}
class BaseFilter: ImageDecorator {
fileprivate var filter: CIFilter?
init(editor: ImageEditor, filterName: String) {
self.filter = CIFilter(name: filterName)
super.init(editor)
}
required init(_ editor: ImageEditor) {
super.init(editor)
}
override func apply() -> UIImage {
let image = super.apply()
let context = CIContext(options: nil)
filter?.setValue(CIImage(image: image), forKey: kCIInputImageKey)
guard let output = filter?.outputImage else { return image }
guard let coreImage = context.createCGImage(output, from: output.extent) else {
return image
}
return UIImage(cgImage: coreImage)
}
override var description: String {
return "BaseFilter"
}
}
class BlurFilter: BaseFilter {
required init(_ editor: ImageEditor) {
super.init(editor: editor, filterName: "CIGaussianBlur")
}
func update(radius: Double) {
filter?.setValue(radius, forKey: "inputRadius")
}
override var description: String {
return "BlurFilter"
}
}
class ColorFilter: BaseFilter {
required init(_ editor: ImageEditor) {
super.init(editor: editor, filterName: "CIColorControls")
}
func update(saturation: Double) {
filter?.setValue(saturation, forKey: "inputSaturation")
}
func update(brightness: Double) {
filter?.setValue(brightness, forKey: "inputBrightness")
}
func update(contrast: Double) {
filter?.setValue(contrast, forKey: "inputContrast")
}
override var description: String {
return "ColorFilter"
}
}
class Resizer: ImageDecorator {
private var xScale: CGFloat = 0
private var yScale: CGFloat = 0
private var hasAlpha = false
convenience init(_ editor: ImageEditor, xScale: CGFloat = 0, yScale: CGFloat = 0, hasAlpha: Bool = false) {
self.init(editor)
self.xScale = xScale
self.yScale = yScale
self.hasAlpha = hasAlpha
}
required init(_ editor: ImageEditor) {
super.init(editor)
}
override func apply() -> UIImage {
let image = super.apply()
let size = image.size.applying(CGAffineTransform(scaleX: xScale, y: yScale))
UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, UIScreen.main.scale)
image.draw(in: CGRect(origin: .zero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage ?? image
}
override var description: String {
return "Resizer"
}
}
class DecoratorRealWorld: XCTestCase {
func testDecoratorReal() {
let image = loadImage()
print("Client: set up an editors stack")
let resizer = Resizer(image, xScale: 0.2, yScale: 0.2)
let blurFilter = BlurFilter(resizer)
blurFilter.update(radius: 2)
let colorFilter = ColorFilter(blurFilter)
colorFilter.update(contrast: 0.53)
colorFilter.update(brightness: 0.12)
colorFilter.update(saturation: 4)
clientCode(editor: colorFilter)
}
func clientCode(editor: ImageEditor) {
let image = editor.apply()
/// كي ترى معاينة للصورة Xcode لاحظ أنك تستطيع إيقاف التنفيذ في .
print("Client: all changes have been applied for \(image)")
}
}
private extension DecoratorRealWorld {
func loadImage() -> UIImage {
let urlString = "https:// refactoring.guru/images/content-public/logos/logo-new-3x.png"
/// لاحظ:
/// لا تحمّل الصور بالطريقة أدناه في شيفرة إنتاج.
guard let url = URL(string: urlString) else {
fatalError("Please enter a valid URL")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Cannot load an image")
}
guard let image = UIImage(data: data) else {
fatalError("Cannot create an image from data")
}
return image
}
}
Output.txt: نتائج التنفيذ
Client: set up an editors stack
BlurFilter applies changes
Resizer applies changes
Image applies changes
Client: all changes have been applied for Image
الاستخدام في لغة TypeScript
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).
يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.
مثال تصوري
يوضح هذا المثال بنية نمط المزخرِف، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة العنصر الأساسي.
*/
interface Component {
operation(): string;
}
/**
* تقدم العناصر الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
*/
class ConcreteComponent implements Component {
public operation(): string {
return 'ConcreteComponent';
}
}
/**
* تتبع فئة المزخرف الأساسي نفس واجهة العناصر الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
* التغليف لكل المزخرِفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين
* عنصر مغلَّف وكذلك أساليب استدعائه.
*/
class Decorator implements Component {
protected component: Component;
constructor(component: Component) {
this.component = component;
}
/**
* يفوض المزخرِف كل المهام إلى العنصر المُغلَّف.
*/
public operation(): string {
return this.component.operation();
}
}
/**
* تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
*/
class ConcreteDecoratorA extends Decorator {
/**
* للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
* الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرِف.
*/
public operation(): string {
return `ConcreteDecoratorA(${super.operation()})`;
}
}
/**
* تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
*/
class ConcreteDecoratorB extends Decorator {
public operation(): string {
return `ConcreteDecoratorB(${super.operation()})`;
}
}
/**
* تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة العنصر ، وهكذا تظل مستقلة عن الفئات الحقيقية
* للعناصر التي تعمل معها.
*/
function clientCode(component: Component) {
// ...
console.log(`RESULT: ${component.operation()}`);
// ...
}
/**
* وهكذا تستطيع شيفرة العميل أن تدعم العناصر البسيطة...
*/
const simple = new ConcreteComponent();
console.log('Client: I\'ve got a simple component:');
clientCode(simple);
console.log('');
/**
* ...إضافة إلى العناصر المزخرَفة.
*
* لاحظ كيف تستطيع المزخرِفات تغليف العناصر البسيطة والمزخرِفات الأخرى كذلك.
*/
const decorator1 = new ConcreteDecoratorA(simple);
const decorator2 = new ConcreteDecoratorB(decorator1);
console.log('Client: Now I\'ve got a decorated component:');
clientCode(decorator2);
Output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: ConcreteComponent
Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
انظر أيضًا
مصادر
- توثيق نمط المزخرِف في موقع refactoring.guru.