• 我对单例模式的理解
  • 兰妮 发表于 2017/3/13 9:58:00 | 分类标签: 设计模式资料 设计模式 单例模式
  • 世界上本来没有设计模式。用的人多了,也就成了设计模式。所以,我们不是严格按照它的定义去执行,可以根据自己的实际场景、需求去变通。领悟了其中的思想,实现属于自己的设计模式。

    你肯定有过这样的体会。某某时候,听人说起**模式。这么牛逼,回去得看看。结果仔细一看原来自己早就是这么用了,只是不知道它还有个这么高大上的名字。当然,专业的名字方便我们业内交流和教学,对技术的发展和传播起着重要的作用。

    废话不多说,和我一起来学习这些高大上的术语吧。本系列《设计模式学习》,通过对传统面向对象编程语言C#和函数为第一等的元素的javascript语言来对比学习加深对设计模式的领悟和运用。

    定义

    单例模式
    个人理解:只能存在一个实例
    官方解释:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    C#代码示例

    示例1

    public static class Singleton
    {
       //TODO
    }

    表激动,它确实不是我们平时使用的单例模式。它只是个静态对象。但是,我觉得也是可以当成单例来使用的。
    当然,肯定有它的不足,不然也不会去搞个单例模式了。

    • 致命的缺点,不能继承类也不能实现接口。
    • 静态类中所有方法、字段必须是静态的。
    • 你无法控制它的初始化。
    • 静态类我们一般都是用来编写和业务无关的基础方法、扩展方法。而单例类是一个实例类,一般和业务相关。

    示例2

    public class Singleton
    {
        public static Singleton singleton = new Singleton();   
    }
    Console.WriteLine(Singleton.singleton.Equals(Singleton.singleton));//true

    其实它是个假单例

    Singleton s1 = new Singleton();
    Singleton s2 = new Singleton();
    Console.WriteLine(s1.Equals(s2));//false

    且有缺点

    • 在类被加载的时候就自动初始化了singleton
    • singleton应该被定义为只读属性

    示例3

    public class Singleton
    {
        public static readonly Singleton singleton = new Singleton();//自读字段
        private Singleton()//禁止初始化
        {
        }
    }

    这是一个比较简单的单例,但是自动化初始变量还是存在

    示例4

    public class Singleton
    {
        public static Singleton singleton = null;
    
        public static Singleton GetSingleton()
        {
            if (singleton == null)
            {
                singleton = new Singleton();
            }
            return singleton;
        }
        private Singleton()//禁止初始化
        {
        }
    }

    如此一来,我们就可以在调用GetSingleton方法的时候再去实例话了。注意:实例化之后singleton变量值不能再被GC回收了,因为它是个静态变量。
    如此就算完事了吗?不,如果多线程同时执行的时候还是会出现多个实例。

    public class Singleton
    {
        public static Singleton singleton = null;
    
        public static Singleton GetSingleton()
        {
            if (singleton == null) //线程二执行到这里singleton == null为true,会继续下面实例Singleton
            {
               //线程一执行到这里
                Thread.Sleep(1000);//假设这还有段耗时逻辑(也可以理解并发极限)
                singleton = new Singleton();
            }
            return singleton;
        }
        private Singleton()//禁止初始化
        {
        }
    }

    所以还需要继续改进

    示例5

    public class Singleton
    {
        public static Singleton singleton = null;
        private static object obj = new object();
    
        public static Singleton GetSingleton()
        {
            if (singleton == null) //下面有锁了为什么还要判断,因为锁会阻塞线程。而singleton被实例化后这个判断永远为false,不在需要锁。
            {
                lock (obj)
                {
                    //这里代码只可能存在一个线程同时到达
                    if (singleton == null)
                    {
                        Thread.Sleep(1000);
                        singleton = new Singleton();
                    } 
                } 
            }
            return singleton;
        }
        private Singleton()//禁止初始化
        {
        }
    }

    这就是我们常见的单例类代码了。当然你也可以改成读取属性的方式。但区别不大。

    public class Singleton
    {
        private static Singleton singleton = null;
        private static object obj = new object(); 
        public static Singleton Instance
        {
            get
            {
                if (singleton == null)
                {
                    lock (obj)
                    {
                        if (singleton == null)
                        {
                            singleton = new Singleton();
                        }
                    }
                }
                return singleton;
            } 
        }
        private Singleton()//禁止初始化
        {
        }
    }

    C#使用场景

    上面用了那么多的笔墨分析单例模式的使用,可是我们在什么场景下使用单例呢?
    最典型的就是配置文件的读取,通常我们的配置文件是在程序第一次启动的时候读取,运行中是不允许修改配置文件的。

    public class ConfigInfo
    {
        private static ConfigInfo singleton = null;
        private static object obj = new object();
        public static ConfigInfo Instance
        {
            get
            {
                if (singleton == null)
                {
                    lock (obj)
                    {
                        if (singleton == null)
                        {
                            singleton = new ConfigInfo(); 
                            //从配置文件读取并赋值
                            singleton.Email = "zhaopeiym@163.com";
                            singleton.EmailUser = "农码一生";
                            singleton.EmailPass = "***********";
                        }
                    }
                }
                return singleton;
            }
        }
    
        public string Email { get; private set; }
        public string EmailUser { get; private set; }
        public string EmailPass { get; private set; } 
    
        private ConfigInfo()//禁止初始化
        {
        }
    }

    调用

    var emailInfo = ConfigInfo.Instance;
    EmailSend(emailInfo.Email,emailInfo.EmailUser,emailInfo.EmailPass);

    好了,C#中的单例模式大概就这样了。

    JS代码示例

    js和C#是不同的,一个是"无类"语言,一个是传统的面向对象语言。而在js中的单例就比较简单了。比如我们熟悉的window对象。
    那么我们怎么在js中实现自己的单例模式呢?方法很多,先来个简单的:

    示例1

    var Singleton = {
        name: "农码一生",
        getName: function () {
            return this.name;
        }  
    }

    这就是一个最简单的单例,通过字面量创建一个对象。看着是不是非常像C#中的静态类?但是,它不存在静态类中的缺点。
    继承毫无压力:

    var Person = {
        age: 27
    }
    
    var Me = Person;
    Me.name = "农码一生";
    Me.getName = function () {
        return this.name;
    }
    Me.getAge = function () {
        return this.age;
    }

    虽然如此,但它并不完美。按理说字段不应该被外界随意修改的。可是js“无类”,更别说私有字段了。幸运的是js中有无处不在的闭包。
    示例2

    var Singleton = (function () {
        var name = "农码一生";   
        return {
            getName: function () {
                return name;
            }
        }
    })();

    如此一来,我们就实现了一个单例模式。经过前面对C#单例的分析,我们希望在使用的时候才去实例话对象怎么办?(且不要小看这个惰性加载,在实际开发中作用可大着呢。)
    示例3

    var Singleton = (function () {
    
        var Person = function () {
            this.name = "农码一生";
        }
        Person.prototype.getName = function () {
            return this.name;
        };
        var instance;
        return {
            getInstance: function () {
                if (!instance) {
                    instance = new Person();
                }
                return instance;
            }
        }
    })();
    
    var person1 = Singleton.getInstance();
    var person2 = Singleton.getInstance();
    console.log(person1 === person2);//true

    这算是js中比较标准的单例模式了。可能有同学会问,之前C#的时候我记得加了lock锁的啊。这里怎么就算比较标准了呢。不要忘记,==js天生的单线程,后台天生的多线程==。这就是区别。
    为了职责的单一,我应该改写成
    示例4

    var Person = function () {
        this.name = "农码一生";
    }
    Person.prototype.getName = function () {
        return this.name;
    };
        
    var Singleton = (function () {
        var instance;
        return {
            getInstance: function () {
                return instance ||  (instance = new Person(););//简化if判断
            }
        }
    })();

    我们很多时候都会使用到单例,那我们可否把一个对象变成单例的过程抽象出来呢。如下:
    示例5

    //通用的创建单例对象的方法
    var getSingle = function (obj) {
        var instance;
        return function () {
            return instance || (instance = new obj());
        }
    };
    
    var PersonA = function () {
        this.name = "农码一生";
    }
    
    var PersonB = function () {
        this.name = "农码爱妹子";
    } 
    
    var singlePersonA = getSingle(PersonA);//获取PersonA的单例
    var singlePersonB = getSingle(PersonB);//获取PersonB的单例
    var a1 = singlePersonA();
    var a2 = singlePersonA();
    var a3 = singlePersonB();
    var a4 = singlePersonB();
    console.log(a1 === a2);//true
    console.log(a3 === a4);//true
    console.log(a1 === a3);//false 

    有没有头晕晕的,习惯就好了。你会说,我直接用最开始的全局变量字面量对象得了,可你不要忘记会造成变量名的污染。

    JS使用场景

    我们在做Tab也切换的时候就可以用到单例模式。在此,我们做个非单例和单例的比较
    示例6非单例:

    //获取tab1的html数据
    var getTab1Html = function () {
        this.url = "/tab/tab1.json";
        //$.get(this.url, function (data) {
        //    //这里获取请求到的数据,然后加载到tab页面
        //}, "json");
        console.log("执行");
    }
    
    var getTab2Html = function () {
        this.url = "/tab/tab2.json";
        //$.get(this.url, function (data) {
        //    //这里获取请求到的数据,然后加载到tab页面
        //}, "json");
        console.log("执行");
    } 
    
    //点击tab1的时候加载tab1的数据
    $("#tab1").on("click", function () {
        getTab1Html();
    })
    $("#tab2").on("click", function () {
        getTab2Html();
    })

    我们发现没点击一次tab的时候会请求一次后台数据,然后加载页面。这是不是有点傻。正确的姿势应该是第一次点击的时候加载,后面不在请求加载。那么我们就可以使用单例模式了。
    示例7单例:

    //获取tab1的html数据
    var getTab1Html = function () {
        this.url = "/tab/tab1.json";
        //$.get(this.url, function (data) {
        //    //这里获取请求到的数据,然后加载到tab页面
        //}, "json");
        console.log("执行");
    }
    
    var getTab2Html = function () {
        this.url = "/tab/tab2.json";
        //$.get(this.url, function (data) {
        //    //这里获取请求到的数据,然后加载到tab页面
        //}, "json");
        console.log("执行");
    } 
    
    var loadTab1 = getSingle(getTab1Html);
    var loadTab2 = getSingle(getTab2Html);
    
    //点击tab1的时候加载tab1的数据
    $("#tab1").on("click", function () {
        loadTab1();
    })
    $("#tab2").on("click", function () {
        loadTab2();
    })
    

    此时,我们无论点击多少此tab。它也只会在第一次点击的时候请求加载页面数据了。
     

    注意:

    • JS中不建议使用全局变量来达到单例的效果
      • 其一,会引起变量名的全局污染
      • 其二,不能惰性加载。
    • C#中不建议使用静态类来达到单例的效果
      • 其一,不能继承类和接口
      • 其二,内部变量和方法必须静态。
    • 单例模式中实例变量要慎用。因为一个单例很可能被多处操作(修改了变量),从而影响的预期效果。
       

    设计模式之所以能成为设计模式,也是在不断尝试、改进后得到的最佳实践而已。所以,我们不需要生搬硬套,适合的才是最好的。在此,关于单例模式的学习到此结束。谢谢您的阅读。

  • 请您注意

    ·自觉遵守:爱国、守法、自律、真实、文明的原则

    ·尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法规

    ·严禁发表危害国家安全,破坏民族团结、国家宗教政策和社会稳定,含侮辱、诽谤、教唆、淫秽等内容的作品

    ·承担一切因您的行为而直接或间接导致的民事或刑事法律责任

    ·您在编程中国社区新闻评论发表的作品,本网站有权在网站内保留、转载、引用或者删除

    ·参与本评论即表明您已经阅读并接受上述条款

  • 感谢本文作者
  • 作者头像
  • 昵称:兰妮
  • 加入时间:2013/7/30 0:00:00
  • TA的签名
  • 这家伙很懒,虾米都没写
  • +进入TA的空间
  • 以下内容也很赞哦
分享按钮