زمان تخمینی مطالعه: 13 دقیقه

الگوی Flyweight یکی از الگوهای طراحی Structural است زیرا این الگو راه‌هایی را برای کاهش تعداد اشیا ارائه می‌دهد و در نتیجه ساختار اشیاء مورد نیاز کاربرد را بهبود می‌بخشد. الگوی Flyweight زمانی که ما نیاز به ایجاد تعداد زیادی از اشیاء مشابه داشته باشیم (مثلاً تعداد 213) استفاده می‌شود. یکی از ویژگی‌های مهم اجسام با وزن سبک تغییر ناپذیر بودن آنهاست. این بدان معنی است که آنها را نمی‌توان پس از ساخت تغییر داد. الگوی Flyweight که با نام Cache هم شناخته می‌شود یک الگوی طراحی است که به کاربر امکان می‌دهد اشیاء بیشتری را در مقدار RAM در دسترس قرار دهد. این الگو با به اشتراک گذاشتن قسمت‌های مشترک حالت بین چندین شیء به جای نگه داشتن تمام داده‌ها در هر شی، کار می‌کند.

Flyweight design pattern

بیان مسئله: فرض کنید تصمیم گرفتید یک بازی ویدیویی ساده بسازید که در آن بازیکنان بر روی نقشه حرکت می‌کنند و به یکدیگر تیراندازی می‌کنند. در این بازی شما تصمیم گرفتید که یک سیستم ذرات واقعی را پیاده‌سازی کنید و آن را به یکی از ویژگی‌های متمایز بازی تبدیل کنید. در ساختار طراحی شده مقادیر زیادی گلوله، موشک و ترکش‌های ناشی از انفجارها باید در سراسر نقشه پرواز کنند و تجربه‌ای هیجان انگیز را به بازیکن ارائه دهند. پس از اتمام مراحل، آخرین commit را انجام داده و بازی را ساختید و آن را برای دوست خود برای تست اجرا فرستادید. اگرچه بازی بدون نقص روی دستگاه شما اجرا می‌شد، اما دوست شما نمی‌توانست برای مدت طولانی بازی کند. در رایانه او، متاسفانه بازی پس از چند دقیقه گیم پلی از کار می‌افتد. پس از صرف چندین ساعت جستجو در لاگ‌های اشکال زدایی، متوجه شدید که بازی به دلیل مقدار ناکافی RAM از کار افتاده است. معلوم شد که سیستم گرافیکی رایانه دوست شما بسیار ضعیف‌تر از رایانه شما است و به همین دلیل است که مشکل در دستگاه او ظاهر شده است.

با بررسی بیشتر متوجه می‌شوید که مشکل واقعی مربوط به سیستم ذرات طراحی شده توسط شما است. هر ذره، مانند یک گلوله، یک موشک یا یک قطعه ترکش با یک جسم جداگانه نشان داده می‌شد که حاوی داده‌های زیادی بود. زمانی که صحنه‌های اکشن و درگیری روی صفحه نمایش به اوج خودمی‌رسد، ذرات تازه ایجاد شده دیگر در حافظه رم باقی‌مانده نمی‌گنجید، بنابراین برنامه از کار می‌افتاد.

Flyweight pattern problem

با بررسی دقیق‌تر کلاس Particle، ممکن است متوجه شوید که فیلدهای رنگ و sprite(تصویری که ذرات را نمایش می‌دهد) نسبت به سایر فیلدها حافظه بیشتری مصرف می‌کنند. بدتر این است که این دو فیلد داده‌های تقریباً یکسانی را در همه ذرات ذخیره می‌کنند. به عنوان مثال، همه گلوله‌ها دارای رنگ و sprite یکسان هستند.

Flyweight pattern solution

بخش‌های دیگر حالت یک ذره، مانند مختصات، بردار حرکت و سرعت، برای هر ذره منحصر به فرد است. به هر حال، مقادیر این فیلدها در طول زمان تغییر می‌کنند. این داده‌ها نشان دهنده زمینه(Context) همیشه در حال تغییری است که ذره در آن وجود دارد، در حالی که رنگ و sprite برای هر ذره ثابت می‌ماند. داده‌های ثابت یک شی معمولاً حالت ذاتی(intrinsic state) نامیده می‌شود. این حالت درون جسم زندگی می‌کند و اشیاء دیگر فقط می‌توانند آن را بخوانند و نه اینکه آن را تغییر دهند. بقیه حالات جسم که اغلب توسط اجسام دیگر “از بیرون” تغییر می‌کند، حالت بیرونی(extrinsic state) نامیده می‌شود. الگوی Flyweight به شما پیشنهاد می‌کند که ذخیره حالت بیرونی را در داخل جسم متوقف کنید. در عوض، شما باید این حالت را به روش‌های خاصی که بر آن تکیه دارند، منتقل کنید. فقط حالت ذاتی درون شی باقی می‌ماند و به شما امکان می‌دهد از آن در زمینه های مختلف استفاده مجدد کنید. در نتیجه، شما به تعداد کمتری از این اشیاء نیاز خواهید داشت، زیرا آنها فقط در حالت ذاتی متفاوت هستند، که تغییرات بسیار کمتری نسبت به حالت بیرونی دارد.

Flyweight pattern solution

اجازه دهید به بازی خودمان برگردیم با فرض اینکه حالت بیرونی را از کلاس ذرات خود استخراج کرده باشیم، تنها سه جسم مختلف برای نشان دادن همه ذرات در بازی کافی است که شامل یک گلوله، یک موشک و یک قطعه ترکش است. همانطور که احتمالاً تا به حال حدس زده‌اید، جسمی که فقط حالت ذاتی را ذخیره می کند، Flyweight نامیده می‌شود.

– ذخیره‌سازی حالت بیرونی

در حالت کلی مقصد حالت بیرونی کجاست؟ آیا باید برخی از کلاس‌ها هنوز باید آن را ذخیره کنند یا نه؟ در بیشتر موارد، به شی کانتینر منتقل می‌شود، که قبل از اعمال الگو، اشیاء را جمع می‌کند. در مورد مثال ما، این شیء اصلی Game است که تمام ذرات را در فیلد particles ذخیره می‌کند. برای انتقال حالت بیرونی به این کلاس، باید چندین فیلد آرایه برای ذخیره مختصات، بردارها و سرعت هر ذره ایجاد کنید. اما این موارد در واقع همه چیز را شامل نمی‌شود. شما به آرایه دیگری برای ذخیره ارجاعات به یک Flyweight خاص که نشان دهنده یک ذره است نیاز دارید. این آرایه‌ها باید همگام باشند تا بتوانید با استفاده از شاخص یکسان به تمام داده‌های یک ذره دسترسی داشته باشید.

Flyweight pattern solution

یک راه حل زیباتر ایجاد یک کلاس زمینه(Context) جداگانه است که حالت بیرونی را همراه با ارجاع به جسم Flyweight ذخیره می‌کند. این رویکرد مستلزم داشتن تنها یک آرایه در کلاس کانتینر است. در این جا سوالی پیش می‌آید که آیا نیازی نیست که به همان اندازه که در ابتدا داشتیم از این اشیاء زمینه‌ای(contextual) داشته باشیم؟ از نظر فنی بله اما نکته اینجاست که این اجسام بسیار کوچکتر از قبل هستند. فیلد‌ها با بیشترین مصرف حافظه به چند جسم Flyweight منتقل شده‌اند. اکنون، هزاران شیء زمینه‌ای کوچک می‌توانند از یک شیء سنگین Flyweight به جای ذخیره هزاران نسخه از داده‌های آن، دوباره استفاده کنند.

– Flyweight و تغییر ناپذیری

از آنجایی که یک شی Flyweight می‌تواند در زمینه‌های مختلف استفاده شود، باید مطمئن شوید که حالت آن قابل تغییر نیست. یک الگوی Flyweight باید حالت خود را فقط یک بار، از طریق پارامترهای سازنده، مقداردهی اولیه کند. نباید هیچ تنظیم کننده یا فیلد عمومی را در معرض اشیاء دیگر قرار دهد.

– کارخانه Flyweight

برای دسترسی راحت‌تر به Flyweight های مختلف، می‌توانید یک روش Factory ایجاد کنید که مجموعه‌ای از اشیاء با Flyweight موجود را مدیریت می‌کند. این روش حالت ذاتی Flyweight مورد نظر را از مشتری می‌پذیرد، به دنبال یک شی Flyweight موجود مطابق با این حالت می‌گردد و در صورت یافتن آن را بر می‌گرداند. در غیر این صورت، Flyweight جدیدی ایجاد می‌کند و آن را به استخر اضافه می‌کند.

چندین گزینه وجود دارد که این روش را می‌توان در آنها قرار داد. واضح‌ترین مکان یک کانتینر Flyweight است. از طرف دیگر، می‌توانید یک کلاس Factory جدید ایجاد کنید. یا می‌توانید روش Factory ای را ثابت کنید و آن را در یک کلاس Flyweight واقعی قرار دهید.

ساختار الگوی Flyweight

در این بخش به بررسی ساختار الگوی Flyweight می‌پردازیم و پیاده‌سازی آن را به صورت UML خواهیم دید. 

شبه کد(Pseudocode)

در این مثال، الگوی Flyweight به کاهش استفاده از حافظه هنگام رندر کردن میلیون‌ها شی درخت روی بوم کمک می‌کند.

Flyweight pattern example

این الگو حالت ذاتی مکرر را از یک کلاس Tree اصلی استخراج می‌کند و آن را به کلاس TreeType می‌برد. اکنون به جای ذخیره داده‌های مشابه در چندین شیء، فقط در چند شیء با Flyweight نگهداری می‌شود و به اشیاء Tree مناسب که به عنوان زمینه عمل می‌کنند، پیوند داده می‌شود. کد مشتری اشیاء درختی جدیدی را با استفاده از کارخانه flyweight ایجاد می‌کند، که پیچیدگی جستجوی شی مناسب و استفاده مجدد از آن در صورت نیاز را در بر می‌گیرد.

// The flyweight class contains a portion of the state of a
// tree. These fields store values that are unique for each
// particular tree. For instance, you won't find here the tree
// coordinates. But the texture and colors shared between many
// trees are here. Since this data is usually BIG, you'd waste a
// lot of memory by keeping it in each tree object. Instead, we
// can extract texture, color and other repeating data into a
// separate object which lots of individual tree objects can
// reference.
class TreeType is
    field name
    field color
    field texture
    constructor TreeType(name, color, texture) { ... }
    method draw(canvas, x, y) is
        // 1. Create a bitmap of a given type, color & texture.
        // 2. Draw the bitmap on the canvas at X and Y coords.

// Flyweight factory decides whether to re-use existing
// flyweight or to create a new object.
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

// The contextual object contains the extrinsic part of the tree
// state. An application can create billions of these since they
// are pretty small: just two integer coordinates and one
// reference field.
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)

// The Tree and the Forest classes are the flyweight's clients.
// You can merge them if you don't plan to develop the Tree
// class any further.
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)

قابلیت‌ها و کاربرد‌ها

نحوه پیاده‌سازی

  1. فیلدهای یک کلاس که تبدیل به Flyweight می‌شود را به دو قسمت تقسیم کنید:
    • حالت ذاتی: فیلدهایی که حاوی داده‌های بدون تغییر هستند که در بسیاری از اشیاء کپی شده‌اند
    • حالت بیرونی: فیلدهایی که حاوی داده‌های متنی منحصر به فرد برای هر شی هستند
  2. فیلدهایی را که حالت ذاتی را در کلاس نشان می‌دهند، رها کنید، اما مطمئن شوید که تغییر ناپذیر هستند. آنها باید مقادیر اولیه خود را فقط در داخل سازنده بگیرند.
  3. متدهایی را که از فیلدهای حالت بیرونی استفاده می‌کنند، مرور کنید. برای هر فیلد استفاده شده در متد، یک پارامتر جدید معرفی کنید و به جای فیلد از آن استفاده کنید.
  4. به صورت اختیاری، یک کلاس کارخانه برای مدیریت مجموعه Flyweight ایجاد کنید. قبل از ایجاد یک Flyweight جدید، باید Flyweight موجود را بررسی کند. هنگامی که کارخانه راه اندازی شد، مشتریان فقط باید Flyweight را از طریق آن درخواست کنند. آنها باید Flyweight مورد نظر را با انتقال حالت ذاتی آن به کارخانه توصیف کنند.
  5. مشتری باید مقادیر حالت بیرونی (زمینه) را ذخیره یا محاسبه کند تا بتواند متدهای اشیاء Flyweight را فراخوانی کند. به منظور راحتی، حالت بیرونی همراه با میدان مرجع Flyweight ممکن است به یک کلاس زمینه جداگانه منتقل شود.

مزایا و معایب الگوی Flyweight

این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:

– مزایا

– معایب

ارتباط با الگوهای دیگر

نمونه کد الگوی Flyweight

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *