آشنایی و نحوه استفاده از Flyweight Design Pattern

Flyweight pattern درباره ایجاد یک منبع (pool) از اشیاست که امکان به اشتراک گذاری اشیای ایجادشده و مصرف کمتر حافظه را فراهم می کند.

Flyweight Design Pattern یکی از الگوهای ساخت یافته معرفی شده توسط GOF می باشد. این الگو با ایجاد منبعی از اشیا امکان اشتراک گذاری اشیای ایجاد شده را فراهم می کند و همچنین باعث کاهش مصرف حافظه می شود.

بنابراین، این الگو دو کار انجام می دهد، از آنجا که این الگو اشیا را یکبار می سازد و در pool ذخیره می کند:

1. کارایی برنامه کاربردی را از جهت ایجاد شی افزایش می دهد، زیرا نیازی به ساختن شی با هر درخواست نمی باشد.

2. مصرف حافظه را کاهش می دهد، زیرا اشیای ساخته شده در حال حاضر در حافظه قرار دارند و به دلیل وجود pool شی جدیدی ساخته نمی شود.

درنهایت، Flyweight Pattern برنامه های کاربردی را از نظر حافظه و سرعت پردازش کاراتر می کنند.

کلاس دیاگرام اولیه Design Pattern:

IFlyweight: طبق چیزی که قرارداد شده، باید با انواع داده مشتق شده مانند concreate flyweight پیاده سازی شود.

FlyweightFactory: یک کلاس factory است که توسط کلاس Client برای گرفتن داده از concreate flyweight استفاده می شود. همچنین این کلاس وظیفه دارد یک pool از اشیای ایجاد شده بسازد. بنابراین، زمانی که یک درخواست برای داده ای که از قبل وجود دارد، می رسد این کلاس داده را از pool برمی گرداند.

ConcerateSharedFlyweight1: این یک پیاده سازی concreate از اینترفیس Flyweight است. همان طور که از نام آن پیداست، داده های بازگشتی از این کلاس بین کلاینت های آن به اشتراک گذاشته می شوند.

ConcerateUnSharedFlyweight1: این نیز، یک پیاده سازی concreate از اینترفیس Flyweight است. همان طور که از نام آن پیداست، داده های بازگشتی از این کلاس به اشتراک گذاشته نمی شوند، یعنی زمانی که کلاینت نیاز به یک شی جدید داشته باشد استفاده می شود.

Client: از پیاده سازی Concerete استفاده کرده، یک نمونه از شیء Decorate می سازد و از امکانات آن استفاده می نماید.

توجه داشته باشید:

UnSharedFlyweight: همیشه لازم نیست و بستگی به نیاز شما دارد، اما SharedFlyweight زمانی که از Flyweight Pattern استفاده می کنید همیشه نیاز است.

کد زیر پیاده سازی Flyweight Design Pattern و کلاس دیاگرام بحث شده می باشد:

namespace BasicFlyweightPattern
{
    #region basic implementation
    public class FlyweightFactory
    {
        public static List<Item> GetStaticItemList(string key)
        {
            IFlyWeight flyWeight = null;
            ICacheManager _objCacheManager = CacheFactory.GetCacheManager();
 
            if (key == "A")
            {
                if (_objCacheManager.Contains("A"))
                {
                    return _objCacheManager["A"] as List<Item>;
                }
                else
                {
                    flyWeight = new ConcerateSharedFlyweight1();
                }
            }
            else if (key == "B")
            {
                if (_objCacheManager.Contains("B"))
                {
                    return _objCacheManager["B"] as List<Item>;
                }
                else
                {
                    flyWeight = new ConcerateSharedFlyweight2();
                }
            }
 
            var list = flyWeight.GetList();
            _objCacheManager.Add(key, list);
            return list;
        }
    }
 
    interface IFlyWeight
    {
        List<Item> GetList();
    }
 
    public class Item
    {
        public int id { get; set; }
        public string desc { get; set; }
    }
 
    public class ConcerateSharedFlyweight1 : IFlyWeight
    {
        private List<Item> ItemList;
 
        public ConcerateSharedFlyweight1()
        {
            ItemList = new List<Item>();
        }
 
        public List<Item> GetList()
        {
            ItemList.Add(new Item { id = 1, desc = "A1" });
            ItemList.Add(new Item { id = 2, desc = "A2" });
            ItemList.Add(new Item { id = 3, desc = "A3" });
            return ItemList;
        }
    }
 
    public class ConcerateSharedFlyweight2 : IFlyWeight
    {
        private List<Item> ItemList;
 
        public ConcerateSharedFlyweight2()
        {
            ItemList = new List<Item>();
        }
 
        public List<Item> GetList()
        {
            ItemList.Add(new Item { id = 1, desc = "B1" });
            ItemList.Add(new Item { id = 2, desc = "B2" });
            ItemList.Add(new Item { id = 3, desc = "B3" });
            return ItemList;
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            List<Item> list =FlyweightFactory.GetStaticItemList("A");
     //List<Item> list = FlyweightFactory.GetStaticItemList("B");           
     foreach (var item in list)
            {
                Console.WriteLine(item.id.ToString() + "  " + item.desc );
           }
 
            Console.ReadLine();
        }
    }
 
}

نکاتی که باید در کد بالا به آن توجه کنید:

1. کلاینت با فراخوانی GetStaticItemList و ارسال کلید به عنوان آرگومان، داده ها را از FlyweightFactory درخواست می کند.

2. FlyweightFactory یک متد استاتیک GetStaticItemList دارد که نمونه هایی از concreate می سازد و اگر این درخواست برای اولین بار باشد، داده ها را می گیرد و در دفعات بعدی از اشیای موجود در pool استفاده می کند.

3. FlyweightFactory با استفاده از Enterprise library یک pool از اشیای ایجاد شده تاکنون را نگه داری می کند. بلاک ها را برای اشتراک گذاری داده ها بین کلاینت های مختلف به طور مثال اشیای مختلف، cach می کند.

4. در پیاده سازی Concreate الگوی Flyweight، یک لیست Hardcoded که به صورت دستی نوشته شده، برگردانده می شود اما در حالت واقعی، داده ها از منابع داده بازیابی می شوند.

5. Item، یک کلاس poco است که لیستی از اشیایی است که کلاس های Flyweight برمی گردانند.

خروجی:

در اینجا، خروجی نتایج یکسان برای A و B نشان می دهد. اگر برای بار دوم یک شی فراخوانی شود، از Cach برگردانده می شود.

مثالی از Flyweught Design Pattern در یک برنامه کاربردی RealTime:

صورت مسئله:

در Web Applicationها، برای نمایش اسامی کشورها، استان ها و یا لیست محصولات و... از DropDown list استفاده می کنیم که این DropDownها قسمتی از یک Multiple screen هستند که توسط چندین کاربر قابل دسترسی هستند.

در این مثال، برای نمایش یک نوع مختلف از لیست برای چندین درخواست از کاربران، سرور باید چندین بار به database server متصل شود که این کار، کارایی برنامه را کاهش می دهد و همچنین حافظه بیشتری برای ایجاد و ذخیره این لیست ها مصرف می کند. 

راه حل:

برای حل این مشکل، باید از Flyweight Design Pattern در برنامه استفاده کنیم.

شکل زیر، کلاس دیاگرام استفاده شده در برنامه را نشان می دهد:

مقایسه با پیاده سازی اولیه:

IFlyweightManager معادل IFlyweight می باشد.

StaticDataListFlyweightFactory نیز معادل FlyweightFactory است.

CountryStaticListManager و ProductStaticListManager هم معادل ConcreateSharedFlyweight1 و ConcreateSharedFlyweight2 هستند.

StaticItem هم مشابه Item عمل می کند.

namespace FlyWeightPattern
{
    class Program
    {
        static void Main(string[] args)
        {

            List<StaticItem> countrylist = StaticDataListFlyWeidhtFactory.GetStaticItemList("Country");
            foreach (var item in countrylist)
            {
                Console.WriteLine(item.id.ToString() + "  " + item.Code + "  " + item.Description);
            }

            Console.ReadLine();
        }
    }
 public class StaticDataListFlyWeidhtFactory
    {
        public static List<StaticItem> GetStaticItemList(string key)
        {
            IFlyWeightManager manager = null;
            ICacheManager _objCacheManager = CacheFactory.GetCacheManager();

            if (key == "Country")
            {
                if (_objCacheManager.Contains("Country"))
                {
                    return _objCacheManager["Country"] as List<StaticItem>;
                }
                else
                {
                    manager = new CountryStaticListManager();
                }
            }
            else if (key == "ProductType")
            {
                if (_objCacheManager.Contains("ProductType"))
                {
                    return _objCacheManager["ProductType"] as List<StaticItem>;
                }
                else
                {
                    manager = new ProductTypeStaticListManager();
                }
            }

            var list = manager.GetList();
            _objCacheManager.Add(key, list);
            return list;
        }
    }

    interface IFlyWeightManager
    {
        List<StaticItem> GetList();
    }

    public class CountryStaticListManager : IFlyWeightManager
    {
        private List<StaticItem> StaticItemList;

        public CountryStaticListManager()
        {
            StaticItemList = new List<StaticItem>();
        }

        public List<StaticItem> GetList()
        {
            StaticItemList.Add(new StaticItem { id = 1, Code = "IND", Description = "India" });
            StaticItemList.Add(new StaticItem { id = 2, Code = "SRL", Description = "Sri Lanka" });
            StaticItemList.Add(new StaticItem { id = 3, Code = "SA", Description = "South Africa" });
            return StaticItemList;
        }
    }

    public class ProductTypeStaticListManager : IFlyWeightManager
    {
        private List<StaticItem> StaticItemList;

        public ProductTypeStaticListManager()
        {
            StaticItemList = new List<StaticItem>();
        }

        public List<StaticItem> GetList()
        {
            StaticItemList.Add(new StaticItem { id = 1, Code = "0123", Description = "Watch" });
            StaticItemList.Add(new StaticItem { id = 2, Code = "0234", Description = "Shoes" });
            StaticItemList.Add(new StaticItem { id = 3, Code = "0345", Description = "" });
            return StaticItemList;
        }
    }

    public class StaticItem
    {
        public int id { get; set; }
        public string Code { get; set; }
        public string Description { get; set; }
    }
}

خروجی:

این کد نیز مانند کدی که در پیاده سازی اولیه، توضیح دادیم عمل می کند. در این کد، انواع مختلف لیست با ارسال کلیدهای مختلف واکشی می شوند. در این مثال، کلیدها "کشور" و "محصول" هستند. در برنامه های دیگر می توان کلیدهای بیشتری تعریف کرد.

پیاده سازی بالا، از اصول SOLID پیروی می کند و تنها یک استثنا وجود دارد که کلاس Factory است. این کلاس زمانی که کسی بخواهد یک کلید اضافه کند، نیاز به تغییر دارد و این قانون را می شکند.

این Pattern برای کسانی که بخواهند، Pool ای از اشیا بسازند و آن را بین کلاینت های مختلف(کلاینت می تواند نرم افزار، کلاس ها یا web applicationها باشند) به اشتراک بگذارد، بسیار مفید است.

آموزش سی شارپ