C#单例设计模式和原型模式学习

C#单例设计模式学习

       首先我们来用一句话解释下单例模式是什么,它就是一种只会把对象实例化一次的设计模式,也就是说,在程序的运行过程中,只会运行一次这个类的构造函数。那到底为什么要用单例呢,我们先来看下面这个类的构造函数。

public FirstSingle()
{
     Thread.Sleep(2000);
}

       这个构造函数非常简单,但是也非常耗费时间。如果我们需要实例化100个这样的对象,那就至少需要200s,不管是开发人员还是客户都会觉得这样太耗费时间了,但是如果在实例这200个对象的过程中,只执行一次构造函数的话,那就非常快了。没错,单例模式就是为了解决这个问题而横空出世的。接下来我会给大家分享几种实现单例模式的方法。

1.1 双层if+lock实现单例

我们先直接上代码,大家觉得有疑惑没关系,我会给大家一点一点地讲解。
private FirstSingle()
{
     Thread.Sleep(2000);
 }

首先将构造函数改成私有的方法,这样它就不会被外界随意调用了。

//使用静态变量
        private static FirstSingle _FirstSingle;
        private static readonly Object lockObject = new object();

        //使用双层if+lock
        public static FirstSingle CreateSingle()
        {
            if (_FirstSingle == null)
            {
                //减小Lock的作用范围
                lock (lockObject)
                {
                    //防止刚开始有多个线程进入创建线程的代码 
                    //在第一个进入实例化对象后释放锁 在等待的第二个会进入 
                    //所以需要再次判断对象是否已被初始化
                    if (_FirstSingle == null)
                        _FirstSingle = new FirstSingle();
                }
            }
            return _FirstSingle;
        }

       我们首先定义了一个_FirstSingle,让它作为返回对象的出口。然后我们定义了一个Object类型的变量lockObject ,因为我们需要用它作为锁的对象,所以它必须是一个静态并且私有的变量。
       在进入CreateSingle()方法后,会首先判断_FirstSingle是否为空,如果不为空就说明已经被实例化了,直接返回就行,这样就避免了多次调用构造函数。如果发现_FirstSingle为空,就需要通过调用构造函数来给它赋值。
       但是这里就存在一个问题,可能存在多个进程同时要来获取对象,例如下面这样

Thread thread = new Thread(() =>
                 {
                     //FirstSingle single = FirstSingle.CreateSingle();
                     FirstSingle single = FirstSingle.CreateSecond();
                     single.show();
                 });
                thread.Start();

       如果我们不使用lock(),那么可能在刚开始时就有多个进程同时去调用构造函数,所以我们必须使用lock()来避免多次调用构造函数。
       可能有人疑惑为什么需要用两个if呢。第一个if是用来当_FirstSingle已经被赋值后直接返回的,第二个if的作用也是为了防止多次被初始化。假设在程序刚开始运行时,_FirstSingle没有被赋值,所以此时有多个进程进入了第一层if中,启动一个进程获取了lock并且进去给_FirstSingle赋值了。在它释放lock后下一个进程会进入lock,实际上此时_FirstSingle已经被上一个进程赋值了,此时若不再判断一次,_FirstSingle仍然会被赋值。

1.2 使用静态构造函数来实现单例

private static FirstSingle _FirstSingle;
private FirstSingle()
      {
          Thread.Sleep(2000);
      }
//该静态构造函数只会被调用一次
      static FirstSingle()
      {
          _FirstSingle = new FirstSingle();
      }
      public static FirstSingle CreateSecond()
      {
          return _FirstSingle;
      }

       这种方法比上面的实现步骤要简单些,只要是系统确保的静态构造函数只会被运行一次,所以就实现了单例。
       但是此时也有一个问题(不要笑),这样虽然能使确保构造函数只被执行一次,但是如果我们要实例化的200个对象的参数值都不同,那还是要通过执行多次构造函数来给不同的对象参数赋值,由此便引出了接下来的内容:原型模式。

2.1 通过浅克隆实现原型模式

       原型模式就是为了解决单例存在的问题而解决的。它本质就是通过单例实例化出一个对象,此时会调用构造函数给各个参数赋值。接下来如果还需要实例化一个对象,就会先将刚才创建的对象作为原型复制一份,再修改参数的值。这样就只执行了一遍构造函数。
我们有一下的几个类

[Serializable]  //添加可序列化的特性
 public class Student
 {
        public string id { get; set; }
        public string name { get; set; }
        public Course course { get; set; }
 }
 [Serializable]
    public class Course
    {
        public string CourseName { get; set; }
    }

我们继续在Student类中添加方法和属性

private static Student _Student = null;

        //系统自动调用且至调用一次私有构造函数
        //私有方法 用于构造
        private Student()
        {
            Thread.Sleep(2000);
            long res = 0;
            for (int i = 0; i < 10000; i++)
            {
                res += i;
            }
            Console.WriteLine("{0}被构造", this.GetType().Name);
        }

        static Student()
        {
            _Student = new Student()
            {
                id = "1",
                name = "lc",
                course = new Course() { CourseName = "" }
            };
        }

        public static Student CreateInstance(string id, string name, string courseName)
        {
            //浅克隆
            //赋值一份构造好的对象出来
            Student stu = (Student)_Student.MemberwiseClone();
            stu.id = id;

            //在修改了string事,是重新创建一个string,并修改原有的变量指向这个新的地址
            //挤string中的=为new  以为string的内容是不可修改的
            stu.name = name;
            //对于非string的引用型变量 只能修改引用值
            stu.course = new Course() { CourseName = courseName };
            return stu;
        }

        public void Study()
        {
            Console.WriteLine("{0}正在学习{1}", this.name, this.course.CourseName);
        }

       可以看到以上代码与单例模式的区别主要是CreateInstance()方法的区别,它并不是直接返回_Student,而是先克隆一份,然后根据传入的参数来给对象的参数赋值。
       细心的同学就会发现,在修改CourseName 时,并不是直接给stu.course.CourseName 赋值,这是因为参数course是一个引用类型的变量,保存的是地址而不是具体内容,如果直接修改,那么所有对象的CourseName 都会被修改。
       那有人可能要问了,string也是引用类型的变零,那为什么可以直接修改呢。其实string的“=”就相当于使用了“new ”,因为string的值是不可修改的,所以当通过“=”给string赋值时,其实是新创建了一个string对象。

2.2 通过深度克隆实现原型模式

       在浅克隆中,对引用型参数的处理不是很方便,那就可以用深克隆了。在这种模式下,其实就是讲原型对象序列化成字符串,然后再将字符串反序列化成指定的对象。这样我们在没有使用构造方法的情况下得到了一个完全独立的对象,随便修改值引用类型变量都不会影响到其他对象。
先上用于序列化和反序列化的代码

//将对象序列化和反序列化实现深克隆
    public class SerializeHelper
    {
        public static string Serializable(Object target)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                new BinaryFormatter().Serialize(stream, target);
                return Convert.ToBase64String(stream.ToArray());
            }
        }

        public static T Derilizable<T>(string target)
        {
            byte[] targetArray = Convert.FromBase64String(target);
            using (MemoryStream stream = new MemoryStream(targetArray))
            {
                return (T)(new BinaryFormatter().Deserialize(stream));
            }
        }

        public static T DeepClone<T>(T t)
        {
            return Derilizable<T>(Serializable(t));
        }

    }

       此时需要注意的是要给被序列化的对象添加可       序列化的属性[Serializable]
接下来修改CreateInstance

 public static Student CreateInstance(string id, string name, string courseName)
        {
            //深度克隆
            Student stu = SerializeHelper.DeepClone<Student>(_Student);
            stu.id = id;
            stu.name = name;
            stu.course.CourseName = courseName;
            return stu;
        }

最后我们来讨论下什么场景下用原型模式呢?就是当对象的克隆比创建省时间的时候。

发布了19 篇原创文章 · 获赞 6 · 访问量 9426

猜你喜欢

转载自blog.csdn.net/u012712556/article/details/91441631
今日推荐