C#设计模式:享元模式(Flyweight Pattern)

一,什么是享元模式?

享元模式(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来实现唯一性

 四,string的享元模式详解

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,享元模式的原理,字符串享元是全局的,不是局部的,在一个进程中,只有一个堆,所以指向同一个地址的字符串是一样的

 

posted @ 2018-04-09 17:53  叶丶梓轩  阅读(352)  评论(0编辑  收藏  举报