目录
单例模式描述
单例模式,Singleton,是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例模式存在的问题:
1.保证一个类只有一个实例。意思就是一个程序里面,只有一个实例,可以创建多次,最终只有一个实例。最常见是控制某些共享资源 (例如数据库,文件,或者日志) 的访问权限。它的运作方式是这样的: 如果你创建了一个对象, 同时过一会儿后你决定再创建一个新对象, 此时你会获得之前已创建的对象, 而不是一个新对象。
2.为该实例提供一个全局访问节点。 还记得你 用过的那些存储重要对象的全局变量吗? 它们在使用上十分方便, 但同时也非常不安全, 因为任何代码都有可能覆盖掉那些变量的内容, 从而引发程序崩溃。和全局变量一样, 单例模式也允许在程序的任何地方访问特定对象。 但是它可以保护该实例不被其他代码覆盖。还有一点: 你不会希望解决同一个问题的代码分散在程序各处的。 因此更好的方式是将其放在同一个类中, 特别是当其他代码已经依赖这个类时更应该如此。
解决方案:
- 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
- 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
使用场景:
1.如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。
2.如果你需要更加严格地控制全局变量, 可以使用单例模式。
优缺点对比:
优点 | 缺点 |
你可以保证一个类只有一个实例 | 违反了单一职责原则。 该模式同时解决了两个问题 |
你获得了一个指向该实例的全局访问节点 | 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等 |
仅在首次请求单例对象时对其进行初始化 | 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象 |
单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式 |
除此之外还听说,饿汉模式,懒汉模式(也叫做懒惰模式)。
饿汉式,表示自己一直很饿,主动去劳动,一开始类加载的时候就已经实例化了,并且创建单例对象,以后只管用。
懒汉式,表示自己很懒,不想动弹,一开始不会实例化,什么时候用就什么时候new,才进行实例化。
所以,此处就可以根据你自己的程序,启动的时候耗资源和时间的实际情况选择。
单线程应用
1.建立单例Singleton类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
//public,公开的,用于外面的类使用时调用
public class Singleton
{
private static Singleton instance; //声明一个静态类字段,用于存储
private Singleton() //private,私有的,防止使用new直接构造
{
MyProperty = someBusinessLogic(1, 2);
}
public static Singleton GetInstance() //public,公开的,用于外面的类,直接调用
{
if (instance == null) //第一次为null会进来, 第二次就不为null,直接返回
{
instance = new Singleton(); //第一次赋值于_instance
}
return instance;
}
public void someBusinessLogic() //建立其他业务逻辑方法,可以在单例中运行,无参数,无返回值
{
Console.WriteLine("常用方法");
}
public int someBusinessLogic(int a, int b) //建立其他业务逻辑方法,可以在单例中运行,有参数,有返回值
{
someBusinessLogic();
return a + b;
}
public int MyProperty { get; }
}
}
2.应用
using System;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
//Singleton s1 = Singleton.GetInstance();
//Singleton s2 = Singleton.GetInstance();
//if (s1 == s2)
//{
// Console.WriteLine("相同");
// s1.someBusinessLogic();
// int c = s1.someBusinessLogic(1, 2);
// Console.WriteLine(c);
// s2.someBusinessLogic();
// int c2 = s2.someBusinessLogic(2, 2);
// Console.WriteLine(c2);
//}
//else
//{
// Console.WriteLine("不相同");
//}
for (int i = 0; i < 5; i++)
{
int a= Singleton.GetInstance().MyProperty;
Console.WriteLine( a);
}
}
}
}
3.结果
可见调用5次,实例只有一个 。
多线程应用
1.建立单例Singleton类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
//public,公开的,用于外面的类使用时调用
public class Singleton
{
private static Singleton instance; //声明一个静态类字段,用于存储
public string TestValue { get; } //用于运行的时候测试是否是同一个单例,真实情况不需要写
private static volatile object _lock = new object(); //readonly,定义和构造的时候可以赋值,其他地方不能赋值
private Singleton(string a) //private,私有的,防止使用new直接构造
{
TestValue = someBusinessLogic(a);
}
public static Singleton GetInstance(string value) //public,公开的,用于外面的类,直接调用
{
if (instance == null) //第一次为null会进来, 第二次就不为null,直接返回
{
lock (_lock) //多线程会进来,如果有值,先锁住
{
if (instance == null) //第一次为null会进来
{
instance = new Singleton(value);
//instance.testValue = value; //测试值
}
}
}
return instance;
}
public string someBusinessLogic(string a) //建立其他业务逻辑方法,可以在单例中运行,无参数,无返回值
{
Console.WriteLine("常用方法");
return a;
}
public int someBusinessLogic(int a, int b) //建立其他业务逻辑方法,可以在单例中运行,有参数,有返回值
{
return a + b;
}
}
}
还可以使用get的方式写,不能传值,此种方式是可以传值的。
2.应用
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Task.Run(() => TestSingleton("1")); //第一个线程
Task.Run(() => TestSingleton("2")); //第二个线程
Console.WriteLine("结束");
}
public static void TestSingleton(string value)
{
Singleton singleton = Singleton.GetInstance(value);
singleton.someBusinessLogic(1,2);
int c = singleton.someBusinessLogic(1, 2);
Console.WriteLine(c);
Console.WriteLine(singleton.TestValue);
}
}
}
3.结果
这个结果有时候1,有时候2。恰恰就说明了,如果第一个线程先执行了,就创建的是1,如果第二个线程先执行,就创建的是2。也可以直接读取TestValue的值,那么someBusinessLogic方法就只执行了一次。
多线程中,还可以使用Lazy<T>的当方式去写。
using ConsoleApp1;
using System;
using System.IO;
using System.Net.Sockets;
class Program
{
static void Main(string[] args)
{
string a = Singleton.Instance.A;
}
}
public sealed class Singleton
{
private static volatile Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance { get { return lazy.Value; } }
private string a;
public string A
{
get { return a; }
}
private Singleton()
{
a = GetA();
}
private string GetA()
{
return Guid.NewGuid().ToString();
}
}