世界上本来没有设计模式
。用的人多了,也就成了设计模式
。所以,我们不是严格按照它的定义去执行,可以根据自己的实际场景、需求去变通。领悟了其中的思想,实现属于自己的设计模式
。
你肯定有过这样的体会。某某时候,听人说起**模式。这么牛逼,回去得看看。结果仔细一看原来自己早就是这么用了,只是不知道它还有个这么高大上的名字。当然,专业的名字方便我们业内交流和教学,对技术的发展和传播起着重要的作用。
废话不多说,和我一起来学习这些高大上的术语吧。本系列《设计模式学习》,通过对传统面向对象编程语言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#中不建议使用静态类来达到单例的效果
-
- 其一,不能继承类和接口
-
- 其二,内部变量和方法必须静态。
- 单例模式中实例变量要慎用。因为一个单例很可能被多处操作(修改了变量),从而影响的预期效果。
设计模式之所以能成为设计模式,也是在不断尝试、改进后得到的最佳实践而已。所以,我们不需要生搬硬套,适合的才是最好的。在此,关于单例模式的学习到此结束。谢谢您的阅读。
本文已同步至索引目录:《设计模式学习》
本文demo:https://github.com/zhaopeiym/BlogDemoCode