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

من موسوعة حسوب
2.6 محتوى
2.7 محتوى
سطر 893: سطر 893:


     /**
     /**
     * Return an existing variation (Flyweight) by given data or create a new
     * يعيد نسخة موجودة فعلًا (وزن ذبابة) من البيانات المعطاة أو
     * one if it doesn't exist yet.
     * ينشئ واحدًا جديدًا.
     */
     */
     public function getVariation(
     public function getVariation(
سطر 914: سطر 914:


     /**
     /**
     * This function helps to generate unique array keys.
     * تساهم هذه الدالة في توليد مفاتيح مصفوفة فريدة.
     */
     */
     private function getKey(array $data): string
     private function getKey(array $data): string
سطر 922: سطر 922:


     /**
     /**
     * Look for a cat in the database using the given query parameters.
     * ابحث عن قطة في قاعدة البيانات باستخدام معامِلات الاستعلام المعطاة.
     */
     */
     public function findCat(array $query)
     public function findCat(array $query)
سطر 936: سطر 936:


/**
/**
  * The client code.
  * شيفرة العميل.
  */
  */
$db = new CatDataBase;
$db = new CatDataBase;
سطر 942: سطر 942:
echo "Client: Let's see what we have in \"cats.csv\".\n";
echo "Client: Let's see what we have in \"cats.csv\".\n";


// To see the real effect of the pattern, you should have a large database with
// لرؤية الأثر الحقيقي للنمط، يجب أن يكون لديك قاعدة بيانات كبيرة بها ملايين
// several millions of records. Feel free to experiment with code to see the
// السجلات، اختبر في الشيفرة كما تشاء لترى المدى الحقيقي للنمط.
// real extent of the pattern.
$handle = fopen(__DIR__ . "/cats.csv", "r");
$handle = fopen(__DIR__ . "/cats.csv", "r");
$row = 0;
$row = 0;
سطر 987: سطر 986:
     $cat->render();
     $cat->render();
}
}
</syntaxhighlight>
==== Output.txt: نتائج التنفيذ ====
<syntaxhighlight>
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.
</syntaxhighlight>
== الاستخدام في لغة بايثون ==
'''المستوى:''' ★ ★ ☆
'''الانتشار:'''  ☆ ☆ ☆
'''أمثلة الاستخدام:''' هدف نمط وزن الذبابة هو تقليل استهلاك الذاكرة، فإن كان برنامجك لا يعاني من قصور في الذاكرة العشوائية فربما تود تجاهل هذا النمط إلى حين.
يمكن ملاحظة نمط وزن الذبابة من خلال أسلوب إنشائي يعيد كائنات محفوظة مسبقًا (cached) بدلًا من إنشاء كائنات جديدة.
===مثال تصوري===
يوضح هذا المثال بنية نمط '''الواجهة'''، ويركز على إجابة الأسئلة التالية:
*ما الفئات التي يتكون منها؟
*ما الأدوار التي تلعبها هذه الفئات؟
*كيف ترتبط عناصر النمط ببعضها؟
==== main.py: مثال تصوري ====
<syntaxhighlight lang="python">
import json
from typing import Dict
class Flyweight():
    """
    The Flyweight stores a common portion of the state (also called intrinsic
    state) that belongs to multiple real business entities. The Flyweight
    accepts the rest of the state (extrinsic state, unique for each entity) via
    its method parameters.
    """
    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():
    """
    The Flyweight Factory creates and manages the Flyweight objects. It ensures
    that flyweights are shared correctly. When the client requests a flyweight,
    the factory either returns an existing instance or creates a new one, if it
    doesn't exist yet.
    """
    _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:
        """
        Returns a Flyweight's string hash for a given state.
        """
        return "_".join(sorted(state))
    def get_flyweight(self, shared_state: Dict) -> Flyweight:
        """
        Returns an existing Flyweight with a given state or creates a new one.
        """
        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])
    # The client code either stores or calculates extrinsic state and passes it
    # to the flyweight's methods.
    flyweight.operation([plates, owner])
if __name__ == "__main__":
    """
    The client code usually creates a bunch of pre-populated flyweights in the
    initialization stage of the application.
    """
    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()
</syntaxhighlight>
</syntaxhighlight>

مراجعة 15:23، 8 أبريل 2019

نمط وزن الذبابة (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) يدير حقلًا من كائنات وزن الذبابة الموجودة، من أجل تسهيل الوصول إلى الأنواع المختلفة من تلك الكائنات، ويقبل الأسلوبُ الحالةَ الجوهريةَ (intrinsic state) لكائن وزن الذبابة المرغوب فيه من عميل ما، ويبحث عن كائن وزن ذبابة في الموجودين لديه يكون مماثلًا لهذه الحالة ويعيده إن وجد، أو ينشئ واحدًا جديدًا ويضيفه إلى الحقل.

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

البنية

ضع الصورة.

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

مثال توضيحي

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

ضع الصورة.

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

// تحتوي فئة وزن الذبابة على جزء من حالة الشجرة، وهذه الحقول تخزن قيمًا
// فريدة لكل شجرة، فمثلًا لن تجد هنا إحداثيات الشجرة، لكن النقش واللون 
// المشترك بين عدة شجرات يكون هنا.
// وبما أن هذه البيانات تكون كبيرة في العادة، فستضيع ذاكرة كثيرة إن
// حفظتها في كل كائن في الشجرة، وبدلًا من ذلك فإننا نستخرج النقش واللون
// وأي بيانات متكررة إلى كائن منفصل يمكن أن ترجع إليه كائنات كثيرة 
// من الشجرة.
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. قسّم حقول الفئة التي ستصبح وزن ذبابة إلى جزئين.
    1. الحالة الجوهرية: الحقول التي تحتوي بيانات لا تتغير ومكررة في كائنات كثيرة.
    2. الحالة المؤقتة: الحقول التي تحتوي على بيانات سياقية فريدة لكل كائن.
  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).

وسنجعل أحد كائنات وزن الذبابة كائنًا مرجعيًا بمجموعة محددة من البيانات تُخزَّن فيه، بدلًا من تخزين نفس البيانات في آلاف من كائنات 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: لقطة للشاشة

ضع الصورة

 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():
    """
    The Flyweight stores a common portion of the state (also called intrinsic
    state) that belongs to multiple real business entities. The Flyweight
    accepts the rest of the state (extrinsic state, unique for each entity) via
    its method parameters.
    """

    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():
    """
    The Flyweight Factory creates and manages the Flyweight objects. It ensures
    that flyweights are shared correctly. When the client requests a flyweight,
    the factory either returns an existing instance or creates a new one, if it
    doesn't exist yet.
    """

    _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:
        """
        Returns a Flyweight's string hash for a given state.
        """

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

    def get_flyweight(self, shared_state: Dict) -> Flyweight:
        """
        Returns an existing Flyweight with a given state or creates a new one.
        """

        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])
    # The client code either stores or calculates extrinsic state and passes it
    # to the flyweight's methods.
    flyweight.operation([plates, owner])


if __name__ == "__main__":
    """
    The client code usually creates a bunch of pre-populated flyweights in the
    initialization stage of the application.
    """

    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()