天天看點

設計模式—享元模式

一,什麼是享元模式?

享元模式(Flyweight Pattern):采用共享技術來避免大量擁有相同内容對象的開銷,主要用于減少建立對象的數量,以減少記憶體占用和提高性能

1,根本的思路就是對象的重用

2,根本的實作邏輯就是簡單工廠+靜态緩存

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static FlyWeightPattern.FlyweightFactory;

namespace FlyWeightPattern
{

    ///享元模式(FlyWeight):采用共享技術來避免大量擁有相同内容對象的開銷
    // 當我們項目中建立很多對象,而且這些對象存在許多相同子產品,這時,我們可以将這些相同的子產品提取出來采用享元模式生成單一對象,再使用這個對象與之前的諸多對象進行配合使用,這樣無疑會節省很多空間。
    class Program
    {
        static void Main(string[] args)
        {
            相同對象執行時,這樣減少建立對象的開銷,
            //for (int i = 0; i < 5; i++)
            //{
            //    Chinese ch = FlyweightFactory.GetChineseObject("中文");
            //    ch.Say();
            //}

            for (int i = 0; i < 5; i++)
            {
                Task.Run(()=> {
                    People ch = FlyweightFactory.GetChineseObject(LanguageType.Chinese);
                    ch.Say();
                });
            }
            for (int i = 0; i < 5; i++)
            {
                Task.Run(() => {
                    People usa = FlyweightFactory.GetChineseObject(LanguageType.USA);
                    usa.Say();
                });
            }
        }
    }
    public abstract class People
    {
        public abstract void Say();
    }
    public class Chinese : People
    {
        public Chinese()
        {
            Console.WriteLine($"{this.GetType().Name}被建立了");
        }
        public override void Say()
        {
            Console.WriteLine("中國人說:你好");
        }
    }
    public class USA : People
    {
        public USA()
        {

            Console.WriteLine($"{this.GetType().Name}被建立了");
        }
        public override void Say()
        {
            Console.WriteLine("USA:Hello");
        }
    }
}           

FlyweightFactory

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FlyWeightPattern
{
    /// <summary>
    /// 1,根本的思路就是對象的重用
    /// 2,根本的實作邏輯就是簡單工廠+靜态緩存
    /// </summary>
    public class FlyweightFactory
    {
        private static Dictionary<LanguageType, People> dic = new Dictionary<LanguageType, People>(); //定義字典來儲存不同的變量,相同的則取出
        public static object dic_Lock = new object();
        public static People GetChineseObject(LanguageType Language)
        {
            People people = null;
            if (!dic.ContainsKey(Language)) //先判空,多并發不需要排序
            {
                lock (dic_Lock) ///線程鎖,當多線程并發時,如果沒有lock或造成dic2已經存在KEY的錯誤
                {
                    if (!dic.ContainsKey(Language))   //定義字典來儲存不同的變量,相同的則取出
                    {
                        switch (Language)
                        {
                            case LanguageType.Chinese:
                                people = new Chinese();
                                break;
                            case LanguageType.USA:
                                people = new USA();
                                break;
                        }
                        dic.Add(Language, people);             //我們将新建立的對象存儲到緩存中
                    }
                }
            }
            return dic[Language];
        }
        public enum LanguageType
        {
            Chinese,
            USA,
        }
    }
}           

在多并發時的享元模式如果沒有加鎖的判斷會出下一下的問題,字典的KEY沖突

設計模式—享元模式

三,我們解析下亨元模式代碼設計思路

1,圍繞減少相同對象的建立設計使我們代碼設計的核心

2,判定對象是否存在,如果存在則取出,否則就建立,這樣避免對象的重複,我們通過Key來實作唯一性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StringDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = "May";
            string str1 = "May";
            ///object.ReferenceEquals這個方法是判斷記憶體位址是否相等
            ///根據string本身的定義,string在指派的時候,會主動開辟一塊不一樣的記憶體空間,但是這裡輸出True
            ///原因:string 是使用享元模式,在指派的時候會去堆中查找看是否存在May,如果存在str1就指向該堆中的記憶體位址,這是CLR記憶體配置設定機制決定的,字元串的享元是全局的,不是局部的
            Console.Write(object.ReferenceEquals(str,str1));  

            str = "June";
            ///按引用類型來了解,str1輸出的應該是June,但是結果這裡輸出的是May,值不變
            ///原因:根據string本身的定義,string在指派的時候,字元串的不可變性, str = "June"等于重新new一個string,會主動開辟一塊不一樣的記憶體空間
            Console.WriteLine(str1);

            string str3 = string.Format("M{0}","ay");
            ///這裡輸出false,因為str3在初始化中還不知道是不是May字元串,是以開辟了一塊新的記憶體,這裡不是享元模式了
            Console.WriteLine(object.ReferenceEquals(str,str3));

            string str4 = "M";
            string str5 =str4+ "ay";
            ///這裡輸出false
            Console.WriteLine(object.ReferenceEquals(str,str5));

            string str6 = "M" + "ay";
            ///這裡輸出True
            ///原因:"M" + "ay"在編譯過程中,編譯器自動編譯成"May", 是以輸出的是True
            Console.WriteLine(object.ReferenceEquals(str, str6));

            Console.ReadKey();
        }
    }
}           

1,String 使用了享元模式初始化,比如我們定義兩個string 都指派是may,按道理來說string都是重新開辟記憶體空間的,

2,我們用判斷記憶體位址去判斷兩個值得記憶體位址是一樣的,原因是string 初始化時去判斷堆中是否有may 了,

3,享元模式的原理,字元串享元是全局的,不是局部的,在一個程序中,隻有一個堆,是以指向同一個位址的字元串是一樣的

繼續閱讀