面向对象编程思想--单例模式

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程思想--单例模式相关的知识,希望对你有一定的参考价值。


来源:农码一生

ccnblogs.com/zhaopei/p/Single.html


世界上本来没有设计模式。用的人多了,也就成了设计模式。所以,我们不是严格按照它的定义去执行,可以根据自己的实际场景、需求去变通。领悟了其中的思想,实现属于自己的设计模式。


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


废话不多说,和我一起来学习这些高大上的术语吧。本系列《设计模式学习》,通过对传统面向对象编程语言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(); 

                        //从配置文件读取并赋值

                    }

                }

            }

            return singleton;

        }

    }

    private ConfigInfo()//禁止初始化

    {

    }

}


调用


var emailInfo = ConfigInfo.Instance;


好了,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#中不建议使用静态类来达到单例的效果


其一,不能继承类和接口


其二,内部变量和方法必须静态。


单例模式中实例变量要慎用。因为一个单例很可能被多处操作(修改了变量),从而影响的预期效果。

 

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


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于面向对象编程思想--单例模式的主要内容,如果未能解决你的问题,请参考以下文章

游戏设计模式——面向数据编程思想

面向对象编程思想-简单工厂模式

面向对象编程思想-代理模式

面向对象编程思想-组合模式

面向对象编程思想-命令模式

面向对象编程思想-策略模式