وزن الذبابة.
وزن الذبابة نمط تصميم هيكلي يتيح لك احتواء عدد أكبر من الكائنات في حجم ذاكرة الوصول العشوائي المتاحة، من خلال مشاركة الأجزاء المشتركة من الحالة بين كائنات متعددة بدلاً من الاحتفاظ بجميع البيانات في كل كائن على حدة.
##Intent
وزن الذبابة هو نمط تصميم هيكلي يتيح لك احتواء عدد أكبر من الكائنات في حجم ذاكرة الوصول العشوائي المتاحة، من خلال مشاركة الأجزاء المشتركة من الحالة بين كائنات متعددة بدلاً من الاحتفاظ بجميع البيانات في كل كائن على حدة.

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

##Solution
إذا تمعّنت في فئة Particle، ستلاحظ أن حقلَي اللون والرمز البصري يستهلكان ذاكرةً أكبر بكثير من باقي الحقول. والأسوأ من ذلك أن هذين الحقلين يخزّنان بيانات متطابقة تقريباً عبر جميع الجسيمات. فمثلاً، لكل الرصاصات اللون ذاته والرمز البصري ذاته.

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

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

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

يستخرج النمط الحالة الجوهرية المتكررة من الفئة الرئيسية Tree وينقلها إلى فئة وزن الذبابة TreeType.
والآن بدلاً من تخزين نفس البيانات في كائنات متعددة، تُحفظ في عدد ضئيل من كائنات وزن الذبابة وتُرتبط بكائنات Tree المناسبة التي تعمل بوصفها سياقات. يُنشئ كود العميل كائنات الأشجار الجديدة باستخدام مصنع وزن الذبابة الذي يُخفي تعقيد البحث عن الكائن المناسب وإعادة استخدامه عند الحاجة.
// تحتوي فئة وزن الذبابة على جزء من حالة الشجرة.
// هذه الحقول تخزّن قيماً فريدة لكل شجرة بعينها.
// لن تجد هنا إحداثيات الشجرة مثلاً. لكن ستجد هنا
// النسيج والألوان المشتركة بين أشجار كثيرة.
// بما أن هذه البيانات ضخمة عادةً، ستهدر كميةً كبيرة
// من الذاكرة بالاحتفاظ بها في كل كائن شجرة.
// بدلاً من ذلك، يمكننا استخراج النسيج واللون وغيرها
// من البيانات المتكررة إلى كائن منفصل ترجع إليه
// كائنات الأشجار الفردية الكثيرة.
class TreeType is
field name
field color
field texture
constructor TreeType(name, color, texture) { ... }
method draw(canvas, x, y) is
// 1. أنشئ صورة نقطية من النوع واللون والنسيج المحددين.
// 2. ارسم الصورة النقطية على اللوحة عند الإحداثيات X وY.
// يقرر مصنع وزن الذبابة ما إذا كان سيُعيد استخدام
// كائن وزن ذبابة موجود أم ينشئ كائناً جديداً.
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)
// فئتا Tree وForest هما عميلا نمط وزن الذبابة.
// يمكنك دمجهما إن لم تكن تخطط لتطوير فئة 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)
##Applicability
استخدم نمط وزن الذبابة فقط عندما يحتاج برنامجك إلى دعم عدد ضخم من الكائنات التي تكاد لا تتسع في ذاكرة الوصول العشوائي المتاحة.
تعتمد فائدة تطبيق النمط اعتماداً كبيراً على طريقة استخدامه ومكانه. وهو أكثر فائدةً في الحالات التالية:
- عندما يحتاج التطبيق إلى إنشاء عدد ضخم من الكائنات المتشابهة
- عندما يستنفد ذلك ذاكرة الوصول العشوائي المتاحة على الجهاز المستهدف
- عندما تحتوي الكائنات على حالات مكررة يمكن استخراجها ومشاركتها بين كائنات متعددة
##How to Implement
-
قسّم حقول الفئة التي ستصبح كائن وزن ذبابة إلى جزأين:
- الحالة الجوهرية: الحقول التي تحتوي على بيانات ثابتة مكررة عبر كائنات كثيرة
- الحالة الخارجية: الحقول التي تحتوي على بيانات سياقية فريدة لكل كائن
-
أبقِ الحقول التي تُمثّل الحالة الجوهرية في الفئة، لكن تأكد من أنها غير قابلة للتعديل. ينبغي أن تأخذ قيمها الأولية فقط داخل المُنشئ.
-
راجع التوابع التي تستخدم حقول الحالة الخارجية. لكل حقل مستخدَم في التابع، أضف معاملاً جديداً واستخدمه بدلاً من الحقل.
-
اختيارياً، أنشئ فئة مصنع لإدارة مجموعة كائنات وزن الذبابة. ينبغي أن يتحقق من وجود كائن وزن ذبابة قبل إنشاء كائن جديد. بمجرد وضع المصنع في مكانه، يجب على العملاء طلب كائنات وزن الذبابة من خلاله فقط. يصفون كائن وزن الذبابة المطلوب بتمرير حالته الجوهرية إلى المصنع.
-
يجب على العميل تخزين أو حساب قيم الحالة الخارجية (السياق) لتتمكن من استدعاء توابع كائنات وزن الذبابة. للتسهيل، يمكن نقل الحالة الخارجية مع حقل الإشارة إلى كائن وزن الذبابة إلى فئة سياق منفصلة.
##Pros & Cons
- يمكنك توفير قدر كبير من ذاكرة الوصول العشوائي، إذا كان برنامجك يحتوي على كميات هائلة من الكائنات المتشابهة.
- قد تستبدل استهلاك ذاكرة الوصول العشوائي بدورات المعالج عندما يحتاج بعض بيانات السياق إلى إعادة حساب في كل مرة يستدعي فيها أحدهم تابع وزن ذبابة.
- يصبح الكود أكثر تعقيداً بكثير. سيتساءل أعضاء الفريق الجدد دائماً عن سبب فصل حالة الكيان بهذه الطريقة.
##Relations with Other Patterns
-
يمكنك تنفيذ العقد الورقية المشتركة في شجرة المركّب بوصفها كائنات وزن ذبابة لتوفير بعض ذاكرة الوصول العشوائي.
-
يُظهر نمط وزن الذبابة كيفية إنشاء كائنات صغيرة كثيرة، في حين يُظهر نمط الواجهة كيفية إنشاء كائن واحد يُمثّل نظاماً فرعياً بأكمله.
-
يُشبه نمط وزن الذبابة نمط المفرد إذا تمكّنت من اختزال جميع الحالات المشتركة للكائنات في كائن وزن ذبابة واحد. غير أن ثمة فرقَين جوهريَّين بين هذَين النمطَين:
- ينبغي أن يكون هناك نسخة واحدة فقط من المفرد، في حين يمكن أن تكون لفئة وزن الذبابة نسخ متعددة بحالات جوهرية مختلفة.
- يمكن أن يكون كائن المفرد قابلاً للتعديل. أما كائنات وزن الذبابة فغير قابلة للتعديل.