نمط وزن الذبابة (Flyweight)
نمط وزن الذبابة (Flyweight) هو نمط تصميم هيكلي يسمح لك بإدخال كائنات أكثر داخل المساحة المتاحة لديك من الذاكرة العشوائية RAM من خلال مشاركة أجزاء مشتركة بين عدة كائنات بدلًا من إبقاء نسخة من البيانات داخل كل كائن.
المشكلة
لنقل أنك قررت كتابة لعبة من أجل التسلية في وقت الفراغ، يتحرك فيها اللاعبون داخل خريطة ويطلقون النار على بعضهم، وقد رأيتَ أن تستخدم نظامًا جزئيًا يجعل تلك اللعبة مميزة من خلال كميات كبيرة من الطلقات النارية والصواريخ والأشلاء التي تتناثر من الانفجارات، كي تحقق تجربة مثيرة للاعبين.
ثم إنك أرسلتها إلى صديق لك ليجربها عندما أنهيتها، لكنه لم يستطع متابعة اللعب لوقت طويل بسبب انهيار اللعبة مرة بعد مرة -رغم أنها تعمل بسلاسة على حاسوبك-، ولما بحثت في سجلات التنقيح (Debug logs) وجدت أن اللعبة تتعطل بسبب عدم كفاية الذاكرة العشوائية، لأن حاسوب صديقك أضعف من حاسوبك، لهذا ظهرت المشكلة عنده سريعًا.
أما حقيقة المشكلة وأصلها، فإن كل جزء في اللعبة -سواء كان رصاصة أو صاروخًا أو شظية صغيرة- قد مُثِّل بكائن منفصل يحمل الكثير من البيانات، وستصل اللعبة حتمًا إلى نقطة تملأ الأشلاء فيها شاشة اللاعب إلى حد أن الأشلاء والشظايا الجديدة لن تجد مكانًا في الذاكرة العشوائية المتبقية، وعندها تتعطل اللعبة وتعلق.
ضع الصورة.
الحل
لعلك لاحظت عند التدقيق في فئة Particle أن اللون وحقول النقوش (Sprites) تستهلك ذاكرة أكثر من الحقول الأخرى، والأسوأ هنا أن هذين الحقلين يخزّنان بيانات متطابقة تقريبًا في كل الأجزاء، فالطلقات مثلًا لديها نفس اللون والنقوش.
ضع الصورة
أما بقية مكونات الجزء نفسه مثل الإحداثيات ومتجهات الحركة والسرعة فتكون فريدة لكل جزء، وتتغير مع الوقت، وتمثل هذه البيانات السياق الذي توجد فيه تلك الأجزاء، والذي بدوره يكون دائم التغير، بينما يظل اللون والنقش ثابتًا لكل جزء.
ويطلق على هذه البيانات الثابتة للجزء عادة اسم الحالة الجوهرية (intrinsic state)، وهي توجد داخل الكائن، وتستطيع الكائنات الأخرى قراءتها فقط دون تغييرها، أما بقية حالة الكائن فإنها تُغيَّر من الخارج فقط في الغالب بواسطة كائنات أخرى، ويطلق عليها عندها الحالة المؤقتة (extrinsic state).
ويقترح نمط وزن الذبابة أن تتوقف عن تخزين الحالة المؤقتة داخل الكائن، وإنما تمرر تلك الحالة إلى أساليب محددة تعتمد عليها، ولا يبقى داخل الكائن سوى الحالة الجوهرية فقط، مما يسمح لك باستخدامه في سياقات مختلفة. وكنتيجة لهذا فستحتاج إلى كائنات أقل بما أنها تختلف فقط في الحالة الجوهرية، والتي بها متغيرات أقل بكثير من الحالة العارضة.
ضع الصورة.
بالعودة إلى لعبتنا، وبافتراض أننا استخرجنا الحالة المؤقتة من فئة الجزء، فستكون ثلاثة كائنات مختلفة كافية لتمثيل كل الأجزاء في اللعبة: رصاصة وصاروخ وقطعة أشلاء، ولعلك خمنت أن ذلك الكائن الذي سيخزن البيانات الجوهرية فقط هو الذي سيطلق عليه وزن الذبابة (flyweight).
تخزين الحالة المؤقتة
أين تذهب الحالة المؤقتة؟ لابد أن تخزنها أليس كذلك؟، في الغالب تنتقل إلى كائن حاوي (container object)، يجمع الكائنات قبل تطبيق النمط. وفي حالتنا فإن هذا الكائن هو كائن Game الأساسي الذي يخزن كل الأجزاء في حقل Particles، وكي ننقل الحالة المؤقتة إلى هذه الفئة نحتاج إلى إنشاء عدة حقول مصفوفات لتخزين الإحداثيات والمتجهات والسرعة لكل جزء على حدة، لكن هذا ليس كل شيء.
فستحتاج مصفوفة أخرى أيضًا لتخزين المراجع إلى كائن "وزن ذبابة" محدد يمثل أحد الأجزاء، وهذه المصفوفات يجب أن تكون متزامنة مع بعضها بحيث تستطيع الوصول إلى كل البيانات لجزء ما باستخدام نفس الفهرس.
ضع الصورة.
ترجمة الصورة. 1: اذهب إلى مصفوفة particles وحاول إيجاد جزء باللون والنقش المطلوبين، إن لم تجد فأنشئ واحدًا جديدًا.
2. أنشئ جزءًا متحركًا بالبيانات المعطاة وكائن الجزء من الخطوة الأولى.
وهناك حل أفضل بأن تنشئ فئة سياق منفصلة تخزن الحالة المؤقتة مع مرجع إلى كائن وزن الذبابة، وسيتطلب هذا المنظور أن يكون لديك مصفوفة وحيدة في الفئة الحاوية (container class).
لكن ألن نحتاج كائنات سياقية كثيرة كما احتجنا في البداية؟ نعم، ولكن هذه الكائنات أصغر بكثير من ذي قبل، وأكثر الحقول المستهلكة للذاكرة قد نُقلت إلى كائنات قليلة من كائنات وزن الذبابة. والآن يستطيع ألف كائن سياقي صغير أن يعيد استخدام كائن وزن ذبابة ثقيل، بدلًا من تخزين ألف نسخة من بياناته.
وزن الذبابة والجمود
بما أن نفس كائن وزن الذبابة يمكن إعادة استخدامه في سياقات مختلفة إلا أنك يجب أن تتأكد أن حالته لا يمكن تغييرها، ذلك أن هذا الكائن يجب أن يبدأ حالته (initialize) مرة واحدة فقط من خلال معامِلاتِ منشئ (Constructor Parameters)، ولا ينبغي أن يكشف أي محدِّدات أو حقول عامة للكائنات الأخرى.
مصنع وزن الذبابة
يمكنك إنشاء أسلوب مصنع (Factory method) يدير كائنات وزن الذبابة الموجودة حاليًا، من أجل تسهيل الوصول إلى الأنواع المختلفة من تلك الكائنات، ويقبل الأسلوب الحالة الجوهرية لكائن وزن الذبابة المرغوب فيه من عميل ما، ويبحث عن كائن وزن ذبابة في الموجودين لديه يكون مماثلًا لهذه الحالة