«Design Patterns/flyweight»: الفرق بين المراجعتين

من موسوعة حسوب
اذهب إلى: تصفح، ابحث
(مصادر)
ط
 
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:نمط وزن الذبابة (Flyweight)}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:نمط وزن الذبابة Flyweight}}</noinclude>
 
نمط وزن الذبابة (Flyweight) هو نمط تصميم هيكلي يسمح لك بزيادة عدد الكائنات التي يمكنك إضافتها داخل نفس المساحة المتاحة لديك من الذاكرة العشوائية RAM، من خلال مشاركة أجزاء مشتركة بين عدة كائنات بدلًا من إبقاء نسخة من البيانات كلها داخل كل كائن.
 
نمط وزن الذبابة (Flyweight) هو نمط تصميم هيكلي يسمح لك بزيادة عدد الكائنات التي يمكنك إضافتها داخل نفس المساحة المتاحة لديك من الذاكرة العشوائية RAM، من خلال مشاركة أجزاء مشتركة بين عدة كائنات بدلًا من إبقاء نسخة من البيانات كلها داخل كل كائن.
  

المراجعة الحالية بتاريخ 13:54، 4 مارس 2020

نمط وزن الذبابة (Flyweight) هو نمط تصميم هيكلي يسمح لك بزيادة عدد الكائنات التي يمكنك إضافتها داخل نفس المساحة المتاحة لديك من الذاكرة العشوائية RAM، من خلال مشاركة أجزاء مشتركة بين عدة كائنات بدلًا من إبقاء نسخة من البيانات كلها داخل كل كائن.

محتويات

المشكلة

(ش.1)

لنقل أنك قررت كتابة لعبة من أجل التسلية في وقت الفراغ، يتحرك فيها اللاعبون داخل خريطة ويطلقون النار على بعضهم، وقد رأيتَ أن تستخدم نظامًا جزئيًا (Particle System) يجعل تلك اللعبة مميزة من خلال كميات كبيرة من الطلقات النارية والصواريخ والأشلاء التي تتناثر من الانفجارات، كي تحقق تجربة مثيرة للاعبين.

ثم إنك أرسلتها إلى صديق لك ليجربها عندما أنهيتها، لكنه لم يستطع متابعة اللعب لوقت طويل بسبب انهيار اللعبة مرة بعد مرة -رغم أنها تعمل بسلاسة على حاسوبك-. ولما بحثت في سجلات التنقيح (Debug logs) وجدت أن اللعبة تتعطل بسبب عدم كفاية الذاكرة العشوائية، لأن حاسوب صديقك أضعف من حاسوبك، لهذا ظهرت المشكلة عنده سريعًا.

أما حقيقة المشكلة وأصلها تعود إلى أن كل جزء في اللعبة -سواء كان رصاصة أو صاروخًا أو شظية صغيرة- قد مُثِّل بكائن منفصل يحمل الكثير من البيانات، وستصل اللعبة حتمًا إلى نقطة تملأ الأشلاء فيها شاشة اللاعب إلى حد أن الأشلاء والشظايا الجديدة لن تجد مكانًا في الذاكرة العشوائية المتبقية، وعندها تتعطل اللعبة وتعلق.

الحل

(ش.2)
لعلك لاحظت عند التدقيق في فئة Particle أن اللون وحقول النقوش (Sprites) تستهلك ذاكرة أكثر من الحقول الأخرى، والأدهى هنا أن هذين الحقلين يخزّنان بيانات متطابقة تقريبًا في كل الأجزاء، فالطلقات مثلًا لديها نفس اللون والنقوش. أما بقية مكونات الجزء نفسه مثل الإحداثيات ومتجهات الحركة والسرعة فتكون فريدة لكل جزء، وتتغير مع الوقت، وتمثل هذه البياناتُ السياقَ الذي توجد فيه تلك الأجزاء، والذي بدوره يكون دائم التغير، بينما يظل اللون والنقش ثابتًا لكل جزء.
(ش.3)

ويطلق على هذه البيانات الثابتة للجزء عادة اسم الحالة الجوهرية (Intrinsic State)، وهي توجد داخل الكائن، وتستطيع الكائنات الأخرى قراءتها فقط دون تغييرها، أما بقية حالة الكائن فإنها تُغيَّر من الخارج فقط في الغالب بواسطة كائنات أخرى، ويطلق عليها عندها الحالة المؤقتة (Extrinsic State).

ويقترح نمط وزن الذبابة أن تتوقف عن تخزين الحالة المؤقتة داخل الكائن، وتمرر تلك الحالة بدلًا من ذلك إلى أساليب محددة تعتمد عليها، ولا يبقى داخل الكائن سوى الحالة الجوهرية فقط، مما يسمح لك باستخدامه في سياقات مختلفة. وكنتيجة لهذا فستحتاج إلى كائنات أقل بما أنها تختلف فقط في الحالة الجوهرية، والتي بها متغيرات أقل بكثير من الحالة المؤقتة.

وبالعودة إلى لعبتنا، وبافتراض أننا استخرجنا الحالة المؤقتة من فئة الجزء، فستكفي ثلاثة كائنات مختلفة لتمثيل كل الأجزاء في اللعبة: رصاصة وصاروخ وقطعة أشلاء، ولعلك خمنت أن ذلك الكائن الذي سيخزن البيانات الجوهرية فقط هو الذي سيطلق عليه وزن الذبابة (flyweight)، وتأتي هذه التسمية من لعبة الملاكمة، وتصف الملاكم الذي يكون وزنه أقل من 50 كجم.

تخزين الحالة المؤقتة

أين تذهب الحالة المؤقتة إذًا؟ لابد أن تخزنها أليس كذلك؟ هي تنتقل في الغالب إلى كائن حاوي (Container Object)، يجمع الكائنات قبل تطبيق النمط. وفي حالتنا فإن هذا الكائن هو كائن Game الأساسي الذي يخزن كل الأجزاء في حقل Particles، ونحتاج إلى إنشاء عدة حقول مصفوفات لتخزين الإحداثيات والمتجهات والسرعة لكل جزء على حدة من أجل نقل الحالة المؤقتة إلى هذه الفئة، لكن هذا ليس كل شيء.

(ش.4) تخزين الحالة المؤقتة.

ذلك أنك ستحتاج مصفوفة أخرى لتخزين المراجع إلى كائن "وزن ذبابة" محدد يمثل أحد الأجزاء، ويجب أن تتزامن هذه المصفوفات مع بعضها بحيث تستطيع الوصول إلى كل البيانات لجزء ما باستخدام نفس الفهرس. وأفضلُ من هذا أن تنشئ فئةَ سياق منفصلة تخزن الحالة المؤقتة مع مرجع إلى كائن وزن الذبابة، وسيتطلب هذا المنظور أن يكون لديك مصفوفة وحيدة في الفئة الحاوية (Container Class).

لكن ألن نحتاج كائنات سياقية كثيرة كما احتجنا في البداية؟ نعم، ولكن هذه الكائنات أصغر بكثير من ذي قبل، وقد نُقلت أكثر الحقول المستهلكة للذاكرة إلى كائنات قليلة من كائنات وزن الذبابة. والآن يستطيع ألف كائن سياقي (Contextual Object) صغير أن يعيد استخدام كائن وزن ذبابة ثقيل، بدلًا من تخزين ألف نسخة من بياناته.

وزن الذبابة والجمود (Immutability)

بما أن نفس كائن وزن الذبابة يمكن إعادة استخدامه في سياقات مختلفة فيجب أن تتأكد أن حالته لا يمكن تغييرها، ذلك أن هذا الكائن يجب أن يبدأ حالته (initialize) مرة واحدة فقط من خلال معامِلاتِ منشئٍ (Constructor Parameters)، ولا ينبغي أن يكشف أي محدِّدات أو حقول عامة للكائنات الأخرى.

مصنع وزن الذبابة

يمكنك إنشاء أسلوب مصنع (Factory method) يدير حقلًا من كائنات وزن الذبابة الموجودة، من أجل تسهيل الوصول إلى الأنواع المختلفة من تلك الكائنات، ويقبل الأسلوبُ الحالةَ الجوهريةَ (Intrinsic State) لكائن وزن الذبابة المرغوب فيه من عميل ما، ويبحث عن كائن وزن ذبابة في الموجودين لديه من المماثلين لهذه الحالة ويعيده إن وجد، أو ينشئ واحدًا جديدًا ويضيفه إلى الحقل.

وأحد أبرز الأماكن التي يمكن استخدام هذه الطريقة فيها هو حاوية وزن الذبابة (Flyweight Container)، لكن تستطيع إنشاء فئة مصنع جديد كحل بديل، أو تجعل أسلوب المصنع ثابتًا (static) وتضعه داخل فئة وزنِ ذبابةِ حقيقية.

البنية

(ش.5)
  1. نمط وزن الذبابة هو مجرد تحسين أو تطوير للحل الموجود مسبقًا، فتأكد قبل استخدامه أن البرنامج يعاني فعلًا من مشكلة في استهلاك ذاكرة RAM بسبب احتوائه على عدد كبير من الكائنات المتشابهة داخل الذاكرة في نفس الوقت، وتأكد أيضًا أن هذه المشكلة لا يمكن حلها بأي شكل آخر.
  2. تحتوي فئة Flyweight (وزن الذبابة) على جزء من حالة الكائن الأصلي يمكن مشاركته بين عدة كائنات ، ويمكن استخدام كائن وزن الذبابة نفسه في سياقات مختلفة، وتسمى الحالة المخزَّنة داخل كائن وزن ذبابة بالجوهرية (Intrinsic)، أما الحالة التي تُمرَّر إلى أساليب وزن الذبابة فيطلق عليها المؤقتة أو العارضة (Extrinsic).
  3. فئة Context (السياق) تحتوي الحالة المؤقتة الفريدة لكل الكائنات الأصلية، وحين يُقرن سياق بأحد كائنات وزن الذبابة فإنه يمثل الحالة الكاملة للكائن الأصلي.
  4. عادة يظل سلوك الكائن الأصلي في فئة وزن الذبابة (Flyweight Class)، ويجب في تلك الحالة على من يستدعي أسلوب وزن الذبابة تمرير الأجزاء المناسبة من الحالة المؤقتة (Extrinsic State) إلى معامِلات الأسلوب. لكن من الناحية الأخرى فيمكن نقل السلوك إلى فئة السياق، والتي ستستخدم كائن وزن الذبابة المرتبط بالكاد على أنه كائن بيانات.
  5. يحسب العميل (Client) الحالة المؤقتة لكائنات وزن الذبابة أو يخزنها، ويرى كائنَ وزن الذبابة على أنه كائنٌ قالبٌ (Template Object) يمكن تهيئته أثناء وقت التشغيل (Runtime) بتمرير بعض البيانات السياقية (Contextual Data) إلى معامِلات من أساليبه.
  6. تدير فئة مصنعُ وزنِ الذبابةِ (Flyweight Factory) حقلًا من كائنات وزن الذبابة الموجودة فعلًا، ولا ينشئ العملاءُ كائنات وزن ذبابة بوجود المصنع، وإنما يستدعونه ويمررون إليه أجزاءً من الحالة الجوهرية لكائن وزن الذبابة المطلوب، ويبحث المصنع في الكائنات الموجودة والتي أنشئت من قبل، ويعيد أحدها مما يطابق المواصفات المذكورة في البحث، أو ينشئ واحدًا جديدًا إن لم يجد.

مثال توضيحي

(ش.6)
يساهم نمط وزن الذبابة في هذا المثال في تقليل استهلاك الذاكرة عند معالجة ملايين الكائنات من الشجرة في مساحة ما. ويستخرج النمطُ الحالةَ الجوهريةَ المتكررة من فئة Tree الأساسية، وينقلها إلى فئة وزن الذبابة TreeType. والآن بدلًا من تخزين نفس البيانات في عدة كائنات فإنها تُحفظ في كائنات قليلة من نوع وزن الذبابة، وتُربط بكائنات Tree المناسبة والتي تتصرف كسياقات (Contexts). وتنشئ شيفرة العميل كائنات شجرية جديدة باستخدام مصنع وزن الذبابة الذي يغلف تعقيد البحث عن الكائن المناسب ويعيد استخدامه إن دعت الحاجة لذلك.
// تحتوي فئة وزن الذبابة على جزء من حالة الشجرة، وهذه الحقول تخزن قيمًا
// فريدة لكل شجرة، فمثلًا لن تجد هنا إحداثيات الشجرة، لكن النقش واللون 
// المشترك بين عدة شجرات يكون هنا.
// وبما أن هذه البيانات تكون كبيرة في العادة، فستضيع ذاكرة كثيرة إن
// حفظتها في كل كائن في الشجرة، وبدلًا من ذلك فإننا نستخرج النقش واللون
// وأي بيانات متكررة إلى كائن منفصل يمكن أن ترجع إليه كائنات كثيرة 
// من الشجرة.
class TreeType is
    field name
    field color
    field texture
    constructor TreeType(name, color, texture) { ... }
    method draw(canvas, x, y) is
        // 1. من معطيات اللون والنوع والنقش bitmap أنشئ ملف.
        // 2. داخل مساحة الرسم Y و X تلك في إحداثيات bitmap ارسم صورة.
        //    (Canvas)

// يقرر مصنع وزن الذبابة ما إن كان سيستخدم كائن وزن الذبابة الموجود 
// أم ينشئ واحدًا جديدًا.
class TreeFactory is
    static field treeTypes: collection of tree types
    static method getTreeType(name, color, texture) is
        type = treeTypes.find(name, color, texture)
        if (type == null)
            type = new TreeType(name, color, texture)
            treeTypes.add(type)
        return type

// يحتوي الكائن السياقي على الجزء المؤقت من حالة الشجرة، ويستطيع 
// التطبيق أن ينشئ مليارات من هذه الكائنات بما أنها صغيرة الحجم، 
// فلا تتكون إلا من إحداثيين رقميين وحقل مرجعي واحد.
class Tree is
    field x,y
    field type: TreeType
    constructor Tree(x, y, type) { ... }
    method draw(canvas) is
        type.draw(canvas, this.x, this.y)

// فئتي Forest و Tree هما عملاء لكائن وزن الذبابة، ويمكنك دمج هذين 
// إن لم تكن تنوي تطوير فئة Tree أكثر مما هي عليه.
class Forest is
    field trees: collection of Trees

    method plantTree(x, y, name, color, texture) is
        type = TreeFactory.getTreeType(name, color, texture)
        tree = new Tree(x, y, type)
        trees.add(tree)

    method draw(canvas) is
        foreach (tree in trees) do
            tree.draw(canvas)

قابلية التطبيق

  • استخدم نمط وزن الذبابة حين يتحتم على تطبيقك أن يدعم عددًا ضخمًا من الكائنات التي تكفيها الذاكرة الحالية بالكاد.

تعتمد فائدة استخدام النمط بشكل كبير على كيف وأين يُستخدم، فهو أكثر فائدة عندما يحتاج تطبيق إلى إنتاج عدد كبير من الكائنات المتشابهة، فهذا يستهلك ذاكرة RAM كبيرة على الجهاز الهدف. وتحتوي الكائنات على حالات متكررة يمكن استخراجها ومشاركتها بين كائنات متعددة.

كيفية الاستخدام

  1. قسّم حقول الفئة التي ستصبح وزن ذبابة إلى جزئين.
    • الحالة الجوهرية: الحقول التي تحتوي بيانات لا تتغير ومكررة في كائنات كثيرة.
    • الحالة المؤقتة: الحقول التي تحتوي على بيانات سياقية فريدة لكل كائن.
  2. اترك الحقول التي تمثل الحالة الجوهرية في الفئة، لكن تأكد أنها لا يمكن تغييرها، إذ ينبغي أن تأخذ قيمها الأولية (initial values) داخل المنشئ (Constructor) فقط.
  3. اذهب إلى الأساليب التي تستخدم حقول الحالة المؤقتة، وأدخل معامِلًا جديدًا لكل حقل مستخدم في الأسلوب، واستخدم ذلك المعامل بدلًا من الحقل.
  4. لديك الخيار في إنشاء فئة مصنع لإدارة حقل من كائنات وزن الذبابة، وينبغي أن تتفقد وجود كائنات وزن ذبابة قبل إنشاء جديد منها. أيضًا، يجب أن يطلب العملاء كائنات وزن الذبابة من خلال المصنع فقط بمجرد أن يكون جاهزًا، فيصفوا الكائن المطلوب بتمرير حالته الجوهرية إلى المصنع.
  5. يجب أن يخزن العميل أو يحسب قيم الحالة المؤقتة (السياق) ليستطيع استدعاء أساليب كائنات وزن الذبابة. وقد تُنقل الحالة المؤقتة مع الحقل المرجعي لكائن وزن الذبابة بداعي التيسير إلى فئة سياق (context) منفصلة.

المزايا والعيوب

المزايا

  • تستطيع حفظ مقدار كبير من ذاكرة RAM بافتراض أن برنامجك به كائنات كثيرة متشابهة.

العيوب

  • قد يعني استخدامك لنمط وزن الذبابة تفضيلك للذاكرة على المعالج إن احتاجت بعض البيانات السياقية إلى إعادة الحساب في كل مرة يستدعي أحدهم أسلوب وزن الذبابة.
  • تصبح الشيفرة أكثر تعقيدًا، ولا يعرف الأفراد الجدد في الفريق سبب وجود حالة كيان ما بشكل منفصل مثلًا.

العلاقات مع الأنماط الأخرى

  • تستطيع استخدام عُقد ورقية مشتركة (shared leaf nodes) من شجرة لنمط المركب، تستطيع استخدامها ككائنات وزن ذبابة من أجل توفير ذاكرة RAM.
  • يوضح نمط وزن الذبابة كيف تصنع الكثير من الكائنات الصغيرة، في حين يريك نمط الواجهة كيف تصنع كائنًا واحدًا يمثل النظام الفرعي بأكمله.
  • إن تمكنت من تقليل كل الحالات المشتركة للكائنات إلى كائن وزن ذبابة واحد، فحينها سيكون وزن الذبابة مشابهًا لنمط المفردة. لكن هناك اختلافان أساسيان بين هذين النمطين:
  1. يجب أن تكون هناك نسخة واحدة فقط من المفردة، بينما يمكن لفئة وزن الذبابة أن تكون لها نسخ كثيرة وبحالات جوهرية مختلفة.
  2. يمكن أن يكون كائن المفردة متغيرًا، بينما كائنات وزن الذبابة غير قابلة للتغير.

الاستخدام في لغة جافا

المستوى: ★ ★ ☆

الانتشار:  ★ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين. تجد أمثلة لنمط وزن الذبابة في مكتبات جافا التالية:

(java.lang.Intefer#valueof(int (وكذلك Boolean - Byte - Character - Short - Long - BigDecimal).

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

إخراج غابة (Rendering a forest)

في هذا المثال سنخرِج غابة من مليون شجرة، وكل شجرة ستُمثَّل بكائنها الخاص الذي فيه بعض بيانات حالتها (الإحداثيات والنقوش، ..) ورغم أن البرنامج ينفذ وظيفته الأساسية إلا أنه يستهلك ذاكرة RAM بشراهة.

وسبب ذلك هو وجود عدد ضخم من كائنات الشجرة التي تحتوي على بيانات متكررة مثل الاسم والنقوش واللون، ولهذا السبب تحديدًا نستطيع تطبيق نمط وزن الذبابة على هذا المثال وتخزين هذه القيم داخل كائنات وزن ذبابة منفصلة (فئة TreeType).

وسنجعل أحد كائنات وزن الذبابة كائنًا مرجعيًا (Reference Object) بمجموعة محددة من البيانات تُخزَّن فيه، بدلًا من تخزين نفس البيانات في آلاف من كائنات Tree. ولن تلاحظ شيفرة العميل أي تغيير بما أن تعقيد إعادة استخدام كائنات وزن الذبابة يختفي داخل مصنع وزن ذبابة (flyweight factory).

الأشجار

 trees/Tree.java: تحتوي حالة فريدة لكل شجرة
package refactoring_guru.flyweight.example.trees;

import java.awt.*;

public class Tree {
    private int x;
    private int y;
    private TreeType type;

    public Tree(int x, int y, TreeType type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }

    public void draw(Graphics g) {
        type.draw(g, x, y);
    }
}
 trees/TreeType.java: تحتوي حالة مشتركة بين عدة أشجار
package refactoring_guru.flyweight.example.trees;

import java.awt.*;

public class TreeType {
    private String name;
    private Color color;
    private String otherTreeData;

    public TreeType(String name, Color color, String otherTreeData) {
        this.name = name;
        this.color = color;
        this.otherTreeData = otherTreeData;
    }

    public void draw(Graphics g, int x, int y) {
        g.setColor(Color.BLACK);
        g.fillRect(x - 1, y, 3, 5);
        g.setColor(color);
        g.fillOval(x - 5, y - 10, 10, 10);
    }
}
 trees/TreeFactory.java: يغلِّف التعقيد الكامن في إنشاء كائن وزن الذبابة
package refactoring_guru.flyweight.example.trees;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

public class TreeFactory {
    static Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String name, Color color, String otherTreeData) {
        TreeType result = treeTypes.get(name);
        if (result == null) {
            result = new TreeType(name, color, otherTreeData);
            treeTypes.put(name, result);
        }
        return result;
    }
}

الغابة

 forest/Forest.java: الغابة التي نرسمها
package refactoring_guru.flyweight.example.forest;

import refactoring_guru.flyweight.example.trees.Tree;
import refactoring_guru.flyweight.example.trees.TreeFactory;
import refactoring_guru.flyweight.example.trees.TreeType;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class Forest extends JFrame {
    private List<Tree> trees = new ArrayList<>();

    public void plantTree(int x, int y, String name, Color color, String otherTreeData) {
        TreeType type = TreeFactory.getTreeType(name, color, otherTreeData);
        Tree tree = new Tree(x, y, type);
        trees.add(tree);
    }

    @Override
    public void paint(Graphics graphics) {
        for (Tree tree : trees) {
            tree.draw(graphics);
        }
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.flyweight.example;

import refactoring_guru.flyweight.example.forest.Forest;

import java.awt.*;

public class Demo {
    static int CANVAS_SIZE = 500;
    static int TREES_TO_DRAW = 1000000;
    static int TREE_TYPES = 2;

    public static void main(String[] args) {
        Forest forest = new Forest();
        for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) {
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Summer Oak", Color.GREEN, "Oak texture stub");
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub");
        }
        forest.setSize(CANVAS_SIZE, CANVAS_SIZE);
        forest.setVisible(true);

        System.out.println(TREES_TO_DRAW + " trees drawn");
        System.out.println("---------------------");
        System.out.println("Memory usage:");
        System.out.println("Tree size (8 bytes) * " + TREES_TO_DRAW);
        System.out.println("+ TreeTypes size (~30 bytes) * " + TREE_TYPES + "");
        System.out.println("---------------------");
        System.out.println("Total: " + ((TREES_TO_DRAW * 8 + TREE_TYPES * 30) / 1024 / 1024) +
                "MB (instead of " + ((TREES_TO_DRAW * 38) / 1024 / 1024) + "MB)");
    }

    private static int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }
}
 OutputDemo.png: لقطة للشاشة

dpfl.OutputDemo.png

 OutputDemo.txt: إحصاءات استهلاك RAM
1000000 trees drawn
---------------------
Memory usage:
Tree size (8 bytes) * 1000000
+ TreeTypes size (~30 bytes) * 2
---------------------
Total: 7MB (instead of 36MB)

الاستخدام في لغة #C

المستوى: ★ ★ ☆

الانتشار:  ★ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

Program.cs: مثال تصوري

using System;
using System.Collections.Generic;
using System.Linq;
// NuGet يمكنك تحميلها من مدير حزم ،Json.NET استخدم مكتبة.
using Newtonsoft.Json;

namespace RefactoringGuru.DesignPatterns.Flyweight.Conceptual
{
    // يخزن كائن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
    // الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
    // كائن وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
    // من خلال معامِلات الأسلوب الخاص به.
    public class Flyweight
    {
        private Car _sharedState;

        public Flyweight(Car car)
        {
            this._sharedState = car;
        }

        public void Operation(Car uniqueState)
        {
            string s = JsonConvert.SerializeObject(this._sharedState);
            string u = JsonConvert.SerializeObject(uniqueState);
            Console.WriteLine($"Flyweight: Displaying shared {s} and unique {u} state.");
        }
    }

    // ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
    // هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
    // يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
    public class FlyweightFactory
    {
        private List<Tuple<Flyweight, string>> flyweights = new List<Tuple<Flyweight, string>>();

        public FlyweightFactory(params Car[] args)
        {
            foreach (var elem in args)
            {
                flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(elem), this.getKey(elem)));
            }
        }

        // لكائن وزن ذبابةٍ لحالة معينة. (String Hash) يُعيد المزيج النصي 
        public string getKey(Car key)
        {
            List<string> elements = new List<string>();

            elements.Add(key.Model);
            elements.Add(key.Color);
            elements.Add(key.Company);

            if (key.Owner != null && key.Number != null)
            {
                elements.Add(key.Number);
                elements.Add(key.Owner);
            }

            elements.Sort();

            return string.Join("_", elements);
        }

        // يعيد كائن وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.

        public Flyweight GetFlyweight(Car sharedState)
        {
            string key = this.getKey(sharedState);

            if (flyweights.Where(t => t.Item2 == key).Count() == 0)
            {
                Console.WriteLine("FlyweightFactory: Can't find a flyweight, creating new one.");
                this.flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(sharedState), key));
            }
            else
            {
                Console.WriteLine("FlyweightFactory: Reusing existing flyweight.");
            }
            return this.flyweights.Where(t => t.Item2 == key).FirstOrDefault().Item1;
        }

        public void listFlyweights()
        {
            var count = flyweights.Count;
            Console.WriteLine($"\nFlyweightFactory: I have {count} flyweights:");
            foreach (var flyweight in flyweights)
            {
                Console.WriteLine(flyweight.Item2);
            }
        }
    }

    public class Car
    {
        public string Owner { get; set; }

        public string Number { get; set; }

        public string Company { get; set; }

        public string Model { get; set; }

        public string Color { get; set; }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
            // أثناء مرحلة البدء للتطبيق.
            var factory = new FlyweightFactory(
                new Car { Company = "Chevrolet", Model = "Camaro2018", Color = "pink" },
                new Car { Company = "Mercedes Benz", Model = "C300", Color = "black" },
                new Car { Company = "Mercedes Benz", Model = "C500", Color = "red" },
                new Car { Company = "BMW", Model = "M5", Color = "red" },
                new Car { Company = "BMW", Model = "X6", Color = "white" }
            );
            factory.listFlyweights();

            addCarToPoliceDatabase(factory, new Car {
                Number = "CL234IR",
                Owner = "James Doe",
                Company = "BMW",
                Model = "M5",
                Color = "red"
            });

            addCarToPoliceDatabase(factory, new Car {
                Number = "CL234IR",
                Owner = "James Doe",
                Company = "BMW",
                Model = "X1",
                Color = "red"
            });

            factory.listFlyweights();
        }
        
        public void addCarToPoliceDatabase(FlyweightFactory factory, Car car)
        {
            Console.WriteLine("\nClient: Adding a car to database.");

            var flyweight = factory.GetFlyweight(new Car {
                Color = car.Color,
                Model = car.Model,
                Company = car.Company
            });
            
            // تخزن شيفرة العميل الحالة المؤقتة أو تحسبها ثم تمررها إلى أساليب 
            // كائن وزن الذبابة.
            flyweight.Operation(car);
        }
    }
}

 Output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:
Camaro2018_Chevrolet_pink
black_C300_Mercedes Benz
C500_Mercedes Benz_red
BMW_M5_red
BMW_white_X6

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared {"Owner":null,"Number":null,"Company":"BMW","Model":"M5","Color":"red"} and unique {"Owner":"James Doe","Number":"CL234IR","Company":"BMW","Model":"M5","Color":"red"} state.

Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared {"Owner":null,"Number":null,"Company":"BMW","Model":"X1","Color":"red"} and unique {"Owner":"James Doe","Number":"CL234IR","Company":"BMW","Model":"X1","Color":"red"} state.

FlyweightFactory: I have 6 flyweights:
Camaro2018_Chevrolet_pink
black_C300_Mercedes Benz
C500_Mercedes Benz_red
BMW_M5_red
BMW_white_X6
BMW_red_X1

 الاستخدام في لغة PHP

المستوى: ★ ★ ☆

الانتشار:  ☆ ☆ ☆

أمثلة الاستخدام: يندر استخدام نمط وزن الذبابة في لغة PHP خاصة بسبب طبيعة هذه اللغة، إذ أن شيفرة PHP تعمل مع جزء من بيانات التطبيق ولا تحمِّله بالكامل إلى الذاكرة في نفس الوقت.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.

 index.php: مثال تصوري 

<?php

namespace RefactoringGuru\Flyweight\Conceptual;

/**
 * يخزن كائن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
 * الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
 * كائن وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
 * من خلال معامِلات الأسلوب الخاص به.
 */
class Flyweight
{
    private $sharedState;

    public function __construct($sharedState)
    {
        $this->sharedState = $sharedState;
    }

    public function operation($uniqueState): void
    {
        $s = json_encode($this->sharedState);
        $u = json_encode($uniqueState);
        echo "Flyweight: Displaying shared ($s) and unique ($u) state.\n";
    }
}

/**
 * ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
 * هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
 * يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
 */
class FlyweightFactory
{
    /**
     * @var Flyweight[]
     */
    private $flyweights = [];

    public function __construct(array $initialFlyweights)
    {
        foreach ($initialFlyweights as $state) {
            $this->flyweights[$this->getKey($state)] = new Flyweight($state);
        }
    }

    /**
     * لكائن وزن ذبابةٍ لحالة معينة (string hash) يعيد المزيج النصي.
     */
    private function getKey(array $state): string
    {
        ksort($state);

        return implode("_", $state);
    }

    /**
     * يعيد كائن وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.
     */
    public function getFlyweight(array $sharedState): Flyweight
    {
        $key = $this->getKey($sharedState);

        if (!isset($this->flyweights[$key])) {
            echo "FlyweightFactory: Can't find a flyweight, creating new one.\n";
            $this->flyweights[$key] = new Flyweight($sharedState);
        } else {
            echo "FlyweightFactory: Reusing existing flyweight.\n";
        }

        return $this->flyweights[$key];
    }

    public function listFlyweights(): void
    {
        $count = count($this->flyweights);
        echo "\nFlyweightFactory: I have $count flyweights:\n";
        foreach ($this->flyweights as $key => $flyweight) {
            echo $key . "\n";
        }
    }
}

/**
 * تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
 * أثناء مرحلة البدء للتطبيق.
 */
$factory = new FlyweightFactory([
    ["Chevrolet", "Camaro2018", "pink"],
    ["Mercedes Benz", "C300", "black"],
    ["Mercedes Benz", "C500", "red"],
    ["BMW", "M5", "red"],
    ["BMW", "X6", "white"],
    // ...
]);
$factory->listFlyweights();

// ...

function addCarToPoliceDatabase(
    FlyweightFactory $ff, $plates, $owner,
    $brand, $model, $color
) {
    echo "\nClient: Adding a car to database.\n";
    $flyweight = $ff->getFlyweight([$brand, $model, $color]);

    // تخزن شيفرة العميل الحالة المؤقتة أو تحسبها ثم تمررها إلى أساليب 
    //كائن وزن الذبابة.
    $flyweight->operation([$plates, $owner]);
}

addCarToPoliceDatabase($factory,
    "CL234IR",
    "James Doe",
    "BMW",
    "M5",
    "red",
);

addCarToPoliceDatabase($factory,
    "CL234IR",
    "James Doe",
    "BMW",
    "X1",
    "red",
);

$factory->listFlyweights();

 Output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state.

Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state.

FlyweightFactory: I have 6 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white
BMW_X1_red

مثال واقعي

لاحظ أن التطبيقات الحقيقية على نمط وزن الذبابة في لغة PHP نادرة جدًا، وهذا بسبب طبيعة PHP ذات الخيط الواحد (single thread)، إذ لا يفترض بك أن تخزن كل كائنات برنامجك في الذاكرة في نفس الوقت داخل نفس الخيط (thread).

ورغم أن فكرة المثال الذي نسوقه هنا لن تراه غالبًا في الحياة العملية، ورغم إمكانية حل مشكلة الذاكرة بتعديل هيكلة البرنامج، إلا أنه يعرض مبدأ عمل النمط في الأمثلة الواقعية.

وسنستخدم نمط وزن الذبابة في المثال لتقليل استهلاك ذاكرة RAM لكائنات في قاعدة بيانات حيوان ما داخل عيادة طبية للقطط فقط.، ويمثَّل كل سجلٍ في قاعدة البيانات بكائنِ قطة، وتتكون بياناته من جزئين:

  1. بيانات (مؤقتة) فريدة، مثل اسم القط وعمره وبيانات المالك.
  2. بيانات (جوهرية) مشتركة، مثل اسم السلالة واللون وشكل النقوش عليه، إلخ.

ويُخزَّن أول جزء مباشرة داخل فئة القط التي تتصرف كسياق، أما الجزء الثاني فيخزَّن بشكل منفصل ويمكن مشاركته بين عدة قطط. وتوجد هذه البيانات المشارَكة داخل فئة CatVariation، وكل القطط التي لديها خصائص متشابهة ترتبط بنفس فئة CatVariation، بدلًا من تخزين البيانات المكررة في كل واحد من كائناتها.

index.php: مثال واقعي

<?php

namespace RefactoringGuru\Flyweight\RealWorld;

/**
 * تمثل كائنات وزن الذبابة البيانات المشارَكة من عدة كائنات Cat
 * وما يلي هو تجميع للسلالة واللون وشكل النقش عليها، إلخ.
 */
class CatVariation
{
    /**
     * ما يسمى بالحالة الجوهرية.
     */
    public $breed;

    public $image;

    public $color;

    public $texture;

    public $fur;

    public $size;

    public function __construct(
        string $breed,
        string $image,
        string $color,
        string $texture,
        string $fur,
        string $size
    ) {
        $this->breed = $breed;
        $this->image = $image;
        $this->color = $color;
        $this->texture = $texture;
        $this->fur = $fur;
        $this->size = $size;
    }

    /**
     * يعرض هذا الأسلوب بيانات القطة، ويقبل الحالة المؤقتة كوسائط،
     * ما بقية الحالة فتخزَّن داخل حقول وزن الذبابة.
     *
     * CatVariation قد تتساءل لماذا وضعنا منطق القطة الأساسي داخل فئة
     * Cat بدلًا من الاحتفاظ بها في فئة.
     *
     * وقد يبدو هذا مربكًا فعلًا، لكن لاحظ أنه في الحالات الواقعية فإن نمط 
     * وزن الذبابة يمكن تطبيقه من البداية او إدخاله قسرًا على برنامج 
     * موجود فعلًا متى أدرك المطورون أنهم يواجهون مشكلة في الذاكرة 
     * العشوائية.
     * وفي الحالة الأخيرة فإنك ستجد نفسك أمام فئات كالتي لدينا هنا، 
     * Cat وقد أعدنا هيكلة برنامج مثالي كانت البيانات فيه داخل فئة
     * ولو كنا استخدمنا نمط وزن الذبابة من البداية لكانت أسماء فئاتنا
     * CatContext و Cat أوضح وأقل إرباكًا، كأن تكون.
     * لكن بأي حال فإن السبب الحقيقي في ضرورة وجود السلوك الأساسي داخل 
     * مصرَّح بها أصلًا Context فئة وزن الذبابة هو أنه قد لا تكون فئة.
     * وقدتُخزَّن البيانات السياقية داخل مصفوفة أو أي هيكل آخر للبيانات، ولن
     * يكون لديك مكان آخر لوضع أساليبك فيه باستثناء فئة وزن الذبابة.
     */
    public function renderProfile(string $name, string  $age, string $owner)
    {
        echo "= $name =\n";
        echo "Age: $age\n";
        echo "Owner: $owner\n";
        echo "Breed: $this->breed\n";
        echo "Image: $this->image\n";
        echo "Color: $this->color\n";
        echo "Texture: $this->texture\n";
    }
}

/**
 * يخزن السياق البيانات الفريدة لكل قطة.
 *
 * يمكن تخصيص فئة مستقلة لتخزين السياق، لكن هذا قد لا يكون ضرورة 
 * دومًا، فقد يخزَّن السياق داخل هيكل بيانات ضخم في شيفرة العميل و
 * يُمرَّر إلى أساليب وزن الذبابة عند الحاجة.
 */
class Cat
{
    /**
     * ما يسمى بالحالة المؤقتة.
     */
    public $name;

    public $age;

    public $owner;

    /**
     * @var CatVariation
     */
    private $variation;

    public function __construct(string $name, string $age, string $owner, CatVariation $variation)
    {
        $this->name = $name;
        $this->age = $age;
        $this->owner = $owner;
        $this->variation = $variation;
    }

    /**
     * لا تملك كل حالتها، فقد تحتاج إلى استخدام بعض Context بما أن كائنات فئة
     * الأساليب المساعدة من أجل تسهيل الأمر عليك، كما في حالة المقارنة بين عدة كائنات
     * Context.
     */
    public function matches(array $query): bool
    {
        foreach ($query as $key => $value) {
            if (property_exists($this, $key)) {
                if ($this->$key != $value) {
                    return false;
                }
            } elseif (property_exists($this->variation, $key)) {
                if ($this->variation->$key != $value) {
                    return false;
                }
            } else {
                return false;
            }
        }

        return true;
    }

    /**
     * أيضًا عدة أساليب اختصار تفوض التنفيذ إلى كائن وزن الذبابة Context قد تعرِّف فئة 
     * وقد تكون تلك الأساليب بقايا للأساليب الحقيقية، استُخرِجت إلى فئة وزن الذبابة
     * خلال إعادة هيكلة ضخمة لنمط وزن الذبابة.
     */
    public function render(): string
    {
        $this->variation->renderProfile($this->name, $this->age, $this->owner);
    }
}

/**
 * Flyweight و Context يخزن مصنع وزن الذبابة كائنات كلًا من فئتي.
 * مخفيًا أي أثر لنمط وزن الذبابة عن العميل.
 */
class CatDataBase
{
    /**
     * (Contexts) قائمة كائنات القطط.
     */
    private $cats = [];

    /**
     * (Flyweights) قائمة الصور المختلفة القطط.
     */
    private $variations = [];

    /**
     * عند إضافة قطة إلى قاعدة البيانات فإننا نبحث أولًا عن نوع موجود لدينا أولًا.
     */
    public function addCat(
        string $name,
        string $age,
        string $owner,
        string $breed,
        string $image,
        string $color,
        string $texture,
        string $fur,
        string $size
    ) {
        $variation =
            $this->getVariation($breed, $image, $color, $texture, $fur, $size);
        $this->cats[] = new Cat($name, $age, $owner, $variation);
        echo "CatDataBase: Added a cat ($name, $breed).\n";
    }

    /**
     * يعيد نسخة موجودة فعلًا (وزن ذبابة) من البيانات المعطاة أو
     * ينشئ واحدًا جديدًا.
     */
    public function getVariation(
        string $breed,
        string $image, $color,
        string $texture,
        string $fur,
        string $size
    ): CatVariation {
        $key = $this->getKey(get_defined_vars());

        if (!isset($this->variations[$key])) {
            $this->variations[$key] =
                new CatVariation($breed, $image, $color, $texture, $fur, $size);
        }

        return $this->variations[$key];
    }

    /**
     * تساهم هذه الدالة في توليد مفاتيح مصفوفة فريدة.
     */
    private function getKey(array $data): string
    {
        return md5(implode("_", $data));
    }

    /**
     * ابحث عن قطة في قاعدة البيانات باستخدام معامِلات الاستعلام المعطاة.
     */
    public function findCat(array $query)
    {
        foreach ($this->cats as $cat) {
            if ($cat->matches($query)) {
                return $cat;
            }
        }
        echo "CatDataBase: Sorry, your query does not yield any results.";
    }
}

/**
 * شيفرة العميل.
 */
$db = new CatDataBase;

echo "Client: Let's see what we have in \"cats.csv\".\n";

// لرؤية الأثر الحقيقي للنمط، يجب أن يكون لديك قاعدة بيانات كبيرة بها ملايين
// السجلات، اختبر في الشيفرة كما تشاء لترى المدى الحقيقي للنمط.
$handle = fopen(__DIR__ . "/cats.csv", "r");
$row = 0;
$columns = [];
while (($data = fgetcsv($handle)) !== false) {
    if ($row == 0) {
        for ($c = 0; $c < count($data); $c++) {
            $columnIndex = $c;
            $columnKey = strtolower($data[$c]);
            $columns[$columnKey] = $columnIndex;
        }
        $row++;
        continue;
    }

    $db->addCat(
        $data[$columns['name']],
        $data[$columns['age']],
        $data[$columns['owner']],
        $data[$columns['breed']],
        $data[$columns['image']],
        $data[$columns['color']],
        $data[$columns['texture']],
        $data[$columns['fur']],
        $data[$columns['size']],
    );
    $row++;
}
fclose($handle);

// ...

echo "\nClient: Let's look for a cat named \"Siri\".\n";
$cat = $db->findCat(['name' => "Siri"]);
if ($cat) {
    $cat->render();
}

echo "\nClient: Let's look for a cat named \"Bob\".\n";
$cat = $db->findCat(['name' => "Bob"]);
if ($cat) {
    $cat->render();
}

 Output.txt: نتائج التنفيذ

Client: Let's see what we have in "cats.csv".
CatDataBase: Added a cat (Steve, Bengal).
CatDataBase: Added a cat (Siri, Domestic short-haired).
CatDataBase: Added a cat (Fluffy, Maine Coon).

Client: Let's look for a cat named "Siri".
= Siri =
Age: 2
Owner: Alexander Shvets
Breed: Domestic short-haired
Image: /cats/domestic-sh.jpg
Color: Black
Texture: Solid

Client: Let's look for a cat named "Bob".
CatDataBase: Sorry, your query does not yield any results.

الاستخدام في لغة بايثون

المستوى: ★ ★ ☆

الانتشار:  ☆ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.py: مثال تصوري 

import json
from typing import Dict


class Flyweight():
    """
    يخزن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
    الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
    وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
    من خلال معامِلات الأسلوب الخاص به.
    """

    def __init__(self, shared_state: str) -> None:
        self._shared_state = shared_state

    def operation(self, unique_state: str) -> None:
        s = json.dumps(self._shared_state)
        u = json.dumps(unique_state)
        print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.", end="")


class FlyweightFactory():
    """
    ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
    هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
    يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
    """

    _flyweights: Dict[str, Flyweight] = {}

    def __init__(self, initial_flyweights: Dict) -> None:
        for state in initial_flyweights:
            self._flyweights[self.get_key(state)] = Flyweight(state)

    def get_key(self, state: Dict) -> str:
        """
        لكائن وزن ذبابة لحالة معينة (string hash) يعيد المزيج النصي.
        """

        return "_".join(sorted(state))

    def get_flyweight(self, shared_state: Dict) -> Flyweight:
        """
        يعيد وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.
        """

        key = self.get_key(shared_state)

        if not self._flyweights.get(key):
            print("FlyweightFactory: Can't find a flyweight, creating new one.")
            self._flyweights[key] = Flyweight(shared_state)
        else:
            print("FlyweightFactory: Reusing existing flyweight.")

        return self._flyweights[key]

    def list_flyweights(self) -> None:
        count = len(self._flyweights)
        print(f"FlyweightFactory: I have {count} flyweights:")
        print("\n".join(map(str, self._flyweights.keys())), end="")


def add_car_to_police_database(
    factory: FlyweightFactory, plates: str, owner: str,
    brand: str, model: str, color: str
) -> None:
    print("\n\nClient: Adding a car to database.")
    flyweight = factory.get_flyweight([brand, model, color])
    # تخزن شيفرة العميل الحالة المؤقتة أو تحسبها ثم تمررها إلى أساليب 
    # كائن وزن الذبابة.
    flyweight.operation([plates, owner])


if __name__ == "__main__":
    """
    تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
    أثناء مرحلة البدء للتطبيق.
    """

    factory = FlyweightFactory([
        ["Chevrolet", "Camaro2018", "pink"],
        ["Mercedes Benz", "C300", "black"],
        ["Mercedes Benz", "C500", "red"],
        ["BMW", "M5", "red"],
        ["BMW", "X6", "white"],
    ])

    factory.list_flyweights()

    add_car_to_police_database(
        factory, "CL234IR", "James Doe", "BMW", "M5", "red")

    add_car_to_police_database(
        factory, "CL234IR", "James Doe", "BMW", "X1", "red")

    print("\n")

    factory.list_flyweights()

 Output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:
Camaro2018_Chevrolet_pink
C300_Mercedes Benz_black
C500_Mercedes Benz_red
BMW_M5_red
BMW_X6_white

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW", "M5", "red"]) and unique (["CL234IR", "James Doe"]) state.

Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW", "X1", "red"]) and unique (["CL234IR", "James Doe"]) state.

FlyweightFactory: I have 6 flyweights:
Camaro2018_Chevrolet_pink
C300_Mercedes Benz_black
C500_Mercedes Benz_red
BMW_M5_red
BMW_X6_white
BMW_X1_red

الاستخدام في لغة روبي

المستوى: ★ ★ ★

الانتشار:  ☆ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.rb: مثال تصوري 

require 'json'

# يخزن كائن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
# الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
# كائن وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
# من خلال معامِلات الأسلوب الخاص به.
class Flyweight
  # @param [String] shared_state
  def initialize(shared_state)
    @shared_state = shared_state
  end

  # @param [String] unique_state
  def operation(unique_state)
    s = @shared_state.to_json
    u = unique_state.to_json
    print "Flyweight: Displaying shared (#{s}) and unique (#{u}) state."
  end
end

# ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
# هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
# يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
class FlyweightFactory
  # @param [Hash] initial_flyweights
  def initialize(initial_flyweights)
    @flyweights = {}
    initial_flyweights.each do |state|
      @flyweights[get_key(state)] = Flyweight.new(state)
    end
  end

  # لكائن وزن ذبابة لحالة معينة (string hash) يعيد المزيج النصي.
  def get_key(state)
    state.sort.join('_')
  end

  # يعيد وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.
  def get_flyweight(shared_state)
    key = get_key(shared_state)

    if !@flyweights.key?(key)
      puts "FlyweightFactory: Can't find a flyweight, creating new one."
      @flyweights[key] = Flyweight.new(shared_state)
    else
      puts 'FlyweightFactory: Reusing existing flyweight.'
    end

    @flyweights[key]
  end

  def list_flyweights
    puts "FlyweightFactory: I have #{@flyweights.size} flyweights:"
    print @flyweights.keys.join("\n")
  end
end

# @param [FlyweightFactory] factory
# @param [String] plates
# @param [String] owner
# @param [String] brand
# @param [String] model
# @param [String] color
def add_car_to_police_database(factory, plates, owner, brand, model, color)
  puts "\n\nClient: Adding a car to database."
  flyweight = factory.get_flyweight([brand, model, color])
  # The client code either stores or calculates extrinsic state and passes it to
  # the flyweight's methods.
  flyweight.operation([plates, owner])
end

# تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
# أثناء مرحلة البدء للتطبيق.

factory = FlyweightFactory.new([
                                 %w[Chevrolet Camaro2018 pink],
                                 ['Mercedes Benz', 'C300', 'black'],
                                 ['Mercedes Benz', 'C500', 'red'],
                                 %w[BMW M5 red],
                                 %w[BMW X6 white]
                               ])

factory.list_flyweights

add_car_to_police_database(factory, 'CL234IR', 'James Doe', 'BMW', 'M5', 'red')

add_car_to_police_database(factory, 'CL234IR', 'James Doe', 'BMW', 'X1', 'red')

puts "\n\n"

factory.list_flyweights

 output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:
Camaro2018_Chevrolet_pink
C300_Mercedes Benz_black
C500_Mercedes Benz_red
BMW_M5_red
BMW_X6_white

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state.

Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state.

FlyweightFactory: I have 6 flyweights:
Camaro2018_Chevrolet_pink
C300_Mercedes Benz_black
C500_Mercedes Benz_red
BMW_M5_red
BMW_X6_white
BMW_X1_red

الاستخدام في لغة Swift

المستوى: ★ ★ ★

الانتشار:  ☆ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 Example.swift: مثال تصوري

import XCTest

/// يخزن كائن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
/// الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
/// كائن وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
/// من خلال معامِلات الأسلوب الخاص به.
class Flyweight {

    private let sharedState: [String]

    init(sharedState: [String]) {
        self.sharedState = sharedState
    }

    func operation(uniqueState: [String]) {
        print("Flyweight: Displaying shared (\(sharedState)) and unique (\(uniqueState) state.\n")
    }
}

/// ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
/// هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
/// يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
class FlyweightFactory {

    private var flyweights: [String: Flyweight]

    init(states: [[String]]) {

        var flyweights = [String: Flyweight]()

        for state in states {
            flyweights[state.key] = Flyweight(sharedState: state)
        }

        self.flyweights = flyweights
    }

    /// يعيد وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.
    func flyweight(for state: [String]) -> Flyweight {

        let key = state.key

        guard let foundFlyweight = flyweights[key] else {

            print("FlyweightFactory: Can't find a flyweight, creating new one.\n")
            let flyweight = Flyweight(sharedState: state)
            flyweights.updateValue(flyweight, forKey: key)
            return flyweight
        }
        print("FlyweightFactory: Reusing existing flyweight.\n")
        return foundFlyweight
    }

    func printFlyweights() {
        print("FlyweightFactory: I have \(flyweights.count) flyweights:\n")
        for item in flyweights {
            print(item.key)
        }
    }
}

extension Array where Element == String {

    /// لكائن وزن ذبابة لحالة معينة (string hash) يعيد المزيج النصي.
    var key: String {
        return self.joined()
    }
}


class FlyweightConceptual: XCTestCase {

    func testFlyweight() {

        /// تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
        /// أثناء مرحلة البدء للتطبيق.

        let factory = FlyweightFactory(states:
        [
            ["Chevrolet", "Camaro2018", "pink"],
            ["Mercedes Benz", "C300", "black"],
            ["Mercedes Benz", "C500", "red"],
            ["BMW", "M5", "red"],
            ["BMW", "X6", "white"]
        ])

        factory.printFlyweights()

        /// ...

        addCarToPoliceDatabase(factory,
                "CL234IR",
                "James Doe",
                "BMW",
                "M5",
                "red")

        addCarToPoliceDatabase(factory,
                "CL234IR",
                "James Doe",
                "BMW",
                "X1",
                "red")

        factory.printFlyweights()
    }

    func addCarToPoliceDatabase(
            _ factory: FlyweightFactory,
            _ plates: String,
            _ owner: String,
            _ brand: String,
            _ model: String,
            _ color: String) {

        print("Client: Adding a car to database.\n")

        let flyweight = factory.flyweight(for: [brand, model, color])

        /// تخزن شيفرة العميل الحالة المؤقتة أو تحسبها ثم تمررها إلى أساليب 
        /// كائن وزن الذبابة.
        flyweight.operation(uniqueState: [plates, owner])
    }
}

 Output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:

Mercedes BenzC500red
ChevroletCamaro2018pink
Mercedes BenzC300black
BMWX6white
BMWM5red
Client: Adding a car to database.

FlyweightFactory: Reusing existing flyweight.

Flyweight: Displaying shared (["BMW", "M5", "red"]) and unique (["CL234IR", "James Doe"] state.

Client: Adding a car to database.

FlyweightFactory: Can't find a flyweight, creating new one.

Flyweight: Displaying shared (["BMW", "X1", "red"]) and unique (["CL234IR", "James Doe"] state.

FlyweightFactory: I have 6 flyweights:

Mercedes BenzC500red
BMWX1red
ChevroletCamaro2018pink
Mercedes BenzC300black
BMWX6white
BMWM5red

مثال واقعي

 Example.swift: مثال واقعي

import XCTest
import UIKit

class FlyweightRealWorld: XCTestCase {

    func testFlyweightRealWorld() {

        let maineCoon = Animal(name: "Maine Coon",
                               country: "USA",
                               type: .cat)

        let sphynx = Animal(name: "Sphynx",
                            country: "Egypt",
                            type: .cat)

        let bulldog = Animal(name: "Bulldog",
                             country: "England",
                             type: .dog)

        print("Client: I created a number of objects to display")

        /// عرض الكائنات للمرة الأولى.

        print("Client: Let's show animals for the 1st time\n")
        display(animals: [maineCoon, sphynx, bulldog])


        /// عرض الكائنات للمرة الثانية.
        ///
        /// لاحظ: الكائن المحفوظ من المظهر سيعاد استخدامه هذه المرة

        print("\nClient: I have a new dog, let's show it the same way!\n")

        let germanShepherd = Animal(name: "German Shepherd",
                              country: "Germany",
                              type: .dog)

        display(animals: [germanShepherd])
    }
}

extension FlyweightRealWorld {

    func display(animals: [Animal]) {

        var cells = loadCells(count: animals.count)

        for index in 0..<animals.count {
            cells[index].update(with: animals[index])
        }

        /// Cells استخدام الخلايا...
    }

    func loadCells(count: Int) -> [Cell] {
        /// يحاكي سلوك عرض الجدول/المجموعة.
        return Array(repeating: Cell(), count: count)
    }
}

enum Type: String {
    case cat
    case dog
}

class Cell {

    private var animal: Animal?

    func update(with animal: Animal) {
        self.animal = animal
        let type = animal.type.rawValue
        let photos = "photos \(animal.appearance.photos.count)"
        print("Cell: Updating an appearance of a \(type)-cell: \(photos)\n")
    }
}

struct Animal: Equatable {

    /// هذا محتوى خارجي يحتوي قيمًا محددة وكائنًا بحالة مشتركة
    ///
    /// لاحظ: كائن المظهر سيُنشأ عند الحاجة.

    let name: String
    let country: String
    let type: Type

    var appearance: Appearance {
        return AppearanceFactory.appearance(for: type)
    }
}

struct Appearance: Equatable {

    /// يحتوي هذا الكائن على مظهر محدد مسبقًا لكل خلية.

    let photos: [UIImage]
    let backgroundColor: UIColor
}

extension Animal: CustomStringConvertible {

    var description: String {
        return "\(name), \(country), \(type.rawValue) + \(appearance.description)"
    }
}

extension Appearance: CustomStringConvertible {

    var description: String {
        return "photos: \(photos.count), \(backgroundColor)"
    }
}

class AppearanceFactory {

    private static var cache = [Type: Appearance]()

    static func appearance(for key: Type) -> Appearance {

        guard cache[key] == nil else {
            print("AppearanceFactory: Reusing an existing \(key.rawValue)-appearance.")
            return cache[key]!
        }

        print("AppearanceFactory: Can't find a cached \(key.rawValue)-object, creating a new one.")

        switch key {
        case .cat:
            cache[key] = catInfo
        case .dog:
            cache[key] = dogInfo
        }

        return cache[key]!
    }
}

extension AppearanceFactory {

    private static var catInfo: Appearance {
        return Appearance(photos: [UIImage()], backgroundColor: .red)
    }

    private static var dogInfo: Appearance {
        return Appearance(photos: [UIImage(), UIImage()], backgroundColor: .blue)
    }
}

 Output.txt: نتائج التنفيذ

Client: I created a number of objects to display
Client: Let's show animals for the 1st time

AppearanceFactory: Can't find a cached cat-object, creating a new one.
Cell: Updating an appearance of a cat-cell: photos 1

AppearanceFactory: Reusing an existing cat-appearance.
Cell: Updating an appearance of a cat-cell: photos 1

AppearanceFactory: Can't find a cached dog-object, creating a new one.
Cell: Updating an appearance of a dog-cell: photos 2


Client: I have a new dog, let's show it the same way!

AppearanceFactory: Reusing an existing dog-appearance.
Cell: Updating an appearance of a dog-cell: photos 2

الاستخدام في لغة TypeScript

المستوى: ★ ★ ★

الانتشار:  ☆ ☆ ☆

أمثلة الاستخدام: هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.

يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.

مثال تصوري

يوضح هذا المثال بنية نمط وزن الذبابة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 index.ts: مثال تصوري

/**
 * يخزن كائن وزن الذبابة جزءًا مشتركًا من الحالة (يطلق عليها أيضًا 
 * الحالة الجوهرية) التي تنتمي إلى كيانات تجارية حقيقية. ويقبل
 * كائن وزن الذبابة بقية الحالة (الحالة المؤقتة الفريدة لكل كيان)
 * من خلال معامِلات الأسلوب الخاص به.
 */
class Flyweight {
    private sharedState: any;

    constructor(sharedState: any) {
        this.sharedState = sharedState;
    }

    public operation(uniqueState): void {
        const s = JSON.stringify(this.sharedState);
        const u = JSON.stringify(uniqueState);
        console.log(`Flyweight: Displaying shared (${s}) and unique (${u}) state.`);
    }
}

/**
 * ينشئ مصنع وزن الذبابة كائنات وزن الذبابة ويديرها، ويضمن مشاركة
 * هذه الكائنات بشكل صحيح. وحين يطلب عميلًا كائنَ وزنِ ذبابةٍ فإن المصنع
 * يعيد نسخة موجودة أو ينشئ كائنًا جديدًا إن لم يكن موجودًا من قبل.
 */
class FlyweightFactory {
    private flyweights: {[key: string]: Flyweight} = <any>{};

    constructor(initialFlyweights: string[][]) {
        for (const state of initialFlyweights) {
            this.flyweights[this.getKey(state)] = new Flyweight(state);
        }
    }

    /**
     * لكائن وزن ذبابة لحالة معينة (string hash) يعيد المزيج النصي.
     */
    private getKey(state: string[]): string {
        return state.join('_');
    }

    /**
     * يعيد وزن ذبابة موجود مع حالة معينة معطاة أو ينشئ واحدًا جديدًا.
     */
    public getFlyweight(sharedState: string[]): Flyweight {
        const key = this.getKey(sharedState);

        if (!(key in this.flyweights)) {
            console.log('FlyweightFactory: Can\'t find a flyweight, creating new one.');
            this.flyweights[key] = new Flyweight(sharedState);
        } else {
            console.log('FlyweightFactory: Reusing existing flyweight.');
        }

        return this.flyweights[key];
    }

    public listFlyweights(): void {
        const count = Object.keys(this.flyweights).length;
        console.log(`\nFlyweightFactory: I have ${count} flyweights:`);
        for (const key in this.flyweights) {
            console.log(key);
        }
    }
}

/**
 * تنشئ شيفرة العميل عادة مجموعة من كائنات وزن الذبابة الجاهزة مسبقًا
 * أثناء مرحلة البدء للتطبيق.
 */
const factory = new FlyweightFactory([
    ['Chevrolet', 'Camaro2018', 'pink'],
    ['Mercedes Benz', 'C300', 'black'],
    ['Mercedes Benz', 'C500', 'red'],
    ['BMW', 'M5', 'red'],
    ['BMW', 'X6', 'white'],
    // ...
]);
factory.listFlyweights();

// ...

function addCarToPoliceDatabase(
    ff: FlyweightFactory, plates: string, owner: string,
    brand: string, model: string, color: string,
) {
    console.log('\nClient: Adding a car to database.');
    const flyweight = ff.getFlyweight([brand, model, color]);

    // تخزن شيفرة العميل الحالة المؤقتة أو تحسبها ثم تمررها إلى أساليب 
    // كائن وزن الذبابة.
    flyweight.operation([plates, owner]);
}

addCarToPoliceDatabase(factory, 'CL234IR', 'James Doe', 'BMW', 'M5', 'red');

addCarToPoliceDatabase(factory, 'CL234IR', 'James Doe', 'BMW', 'X1', 'red');

factory.listFlyweights();

 Output.txt: نتائج التنفيذ

FlyweightFactory: I have 5 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white

Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state.

Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state.

FlyweightFactory: I have 6 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white
BMW_X1_red

انظر أيضًا

مصادر