设计模式 1/23 单例模式

为什么将单例模式排名第一,很简单,面试的时候聊到设计模式,大概率就从单例模式开始入手,循循渐进。

当然,我们学习单例模式不是为了去应付面试,而是学好设计模式后,融汇贯通,应用于我们的设计,开发,项目中。

单例模式是最简单的设计模式之一

单例模式【Singleton Pattern】:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

两层含义:1,有且仅有一个实例

     2,有一个访问他的全局访问点

我们来想象一个场景,放眼全球,所有的党派,都只有一个主席(这应该没有例外吧)这个案例,如果要拜访他,是不是需要一个全局的访问点。

对于一个类来说,我们怎样保证他仅有一个实例,初步想到的是不准外部实例化,因为如果可以外部实例化,我就可以实例化无数个。那不准外部实例化,就只能内部了,那就动手修改内部的构造方法。

(敲黑板,划重点) 所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效

如此,我们只需要显示的修改构造方法即可,既然不准外部调用,那我们用private修饰构造方法即可

第一步,控制外部实例化我们就做到了

    /// <summary>
    /// 党派
    /// </summary>
    public class Party
    {
        /// <summary>
        /// 私有化构造函数,不准外部实例化
        /// </summary>
        private Party()
        {
        }
    }
}
View Code

那我们需要一个主席,一个党派总不能群龙无首吧,于是我们定义一个主席,并且我们要提供一个访问的点吧,不然别人想和主席聊两句都找不到人

第二步,创建一个主席,并提供一个访问点

    /// <summary>
    /// 党派
    /// </summary>
    public class Party
    {
        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman;

        /// <summary>
        /// 私有化构造函数,不准外部实例化
        /// </summary>
        private Party()
        {
        }

        /// <summary>
        /// 找主席
        /// </summary>
        /// <returns></returns>
        public static Party GetChairman()
        {
            if (_chairman == null)//主席不存在则创建一个主席
            {
                _chairman = new Party();
            }
            return _chairman;
        }
    }
}
View Code

哇哇哇~!!!私有构造函数,为什么这里可以调用啊,不是不让调用么!!!这是同一个类中,同一个类中,私有,公有,保护,都可以随意调用。

我们再让主席讲一句话

    /// <summary>
    /// 党派
    /// </summary>
    public class Party
    {
        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman;

        /// <summary>
        /// 私有化构造函数,不准外部实例化
        /// </summary>
        private Party()
        {
        }

        /// <summary>
        /// 找主席
        /// </summary>
        /// <returns></returns>
        public static Party GetChairman()
        {
            if (_chairman == null)//主席不存在则创建一个主席
            {
                _chairman = new Party();
            }
            return _chairman;
        }

        public void Say()
        {
            Console.WriteLine("同志们好!");
        }
    }
View Code

这样我们的一个简单的单单例模式就算完成了,我们再看看如何调用

 class Program
    {
        static void Main(string[] args)
        {
            Party chairman = Party.GetChairman();
            chairman.Say();
        }
    }
View Code

那么问题来了,单线程操作的时候,以上没有问题,那多线程的时候呢,多个人同时访问主席,会不会可能产生多个主席呢,多个主席,是不是就违反了单例模式最基本的原则,仅有一个实例呢。多个主席,难道打一架,胜者为王么?

于是我们需要再次优化我们的代码

    /// <summary>
    /// 党派
    /// </summary>
    public class Party
    {
        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman;

        /// <summary>
        /// 静态只读的进程辅助对象
        /// </summary>
        private static readonly object syncRoot = new object();

        /// <summary>
        /// 私有化构造函数,不准外部实例化
        /// </summary>
        private Party()
        {
        }

        /// <summary>
        /// 找主席
        /// </summary>
        /// <returns></returns>
        public static Party GetChairman()
        {
            lock (syncRoot)//锁你丫的
            {
                if (_chairman == null)//主席不存在则创建一个主席
                {
                    _chairman = new Party();
                }
            }
            return _chairman;
        }

        public void Say()
        {
            Console.WriteLine("同志们好!");
        }
    }
}
View Code

以上,再同一个时刻加了锁的那部分代码,只有一个线程可以进入。

如果lock不懂,我知道你们懒

lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放

 

那么问题又来了,为什么每一次都要锁,为什么,见一次主席那么那么难么?能不能没有创建的时候我才锁,有主席的时候我直接访问啊

当然可以,这就是双重锁定

    /// <summary>
    /// 党派
    /// </summary>
    public class Party
    {
        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman;

        /// <summary>
        /// 静态只读的进程辅助对象
        /// </summary>
        private static readonly object syncRoot = new object();

        /// <summary>
        /// 私有化构造函数,不准外部实例化
        /// </summary>
        private Party()
        {
        }

        /// <summary>
        /// 找主席
        /// </summary>
        /// <returns></returns>
        public static Party GetChairman()
        {
            if (_chairman == null) //主席不存在,我再锁定,进行选举
            {
                lock (syncRoot) //锁你丫的
                {
                    if (_chairman == null) //主席不存在则创建一个主席
                    {
                        _chairman = new Party();
                    }
                }
            }
            return _chairman;
        }

        public void Say()
        {
            Console.WriteLine("同志们好!");
        }
    }
}
View Code

这里我们就先判断了主席是否存在,不存在,我们再闭门选举,等我们选举出来了,你再来访问。

我们既优化了执行,也保证了仅有一个主席,保证了多线程访问的安全性。

其实还有什么饿汉式,懒汉式的区别

从字面理解

饿汉,一开始我就实例化自己

        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman = new Party();

懒汉,被引用时候,才实例化自己

        /// <summary>
        /// 我是党派主席
        /// </summary>
        private static Party _chairman ;
View Code

具体使用哪一种,应该根据实际情况来定,无优劣之分,值得注意的是线程安全


总结下

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。

2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景: 

1、要求生产唯一序列号。

2、计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。

3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

以上就是关于单例模式的分享

一路前行,风雨无阻

 

posted @ 2017-08-28 23:58  Lionel Andrés Messi  阅读(982)  评论(1编辑  收藏  举报