如果你也喜欢C#开发或者.NET开发,可以关注我,我会一直更新相关内容,并且会是超级详细的教程,只要你有耐心,基本上不会有什么问题,如果有不懂的,也可以私信我加我联系方式,我将毫无保留的将我的经验和技术分享给你,不为其他,只为有更多的人进度代码的世界,而进入代码的世界,最快捷和最容易的就是C#.NET,准备好了,就随我加入代码的世界吧!
一、模式简介
单例模式是一种设计模式,它限制一个类只能创建一个实例,确保在全局范围内都能通过该实例访问该类的唯一对象。
在单例模式中,类的构造方法是私有的,这意味着不能通过 new 关键字来创建类的实例。而是通过一个静态方法获取该类的唯一实例。
单例模式有以下特点:
- 保证一个类只有一个实例;
- 提供一个全局访问点,方便其他类获取该实例;
- 对该实例进行控制,确保只有一个实例存在。
单例模式的实现可以有多种方式,常见的有饿汉式、懒汉式、双重校验锁等。不同的实现方式有不同的优缺点,需要根据具体情况选择合适的方式。
二、为什么要学习单例模式
2.1 简化对象的创建和管理
单例模式保证了一个类只有一个实例存在,可以节省内存和系统资源的开销,使得对象的创建和管理更加简单。
2.2 全局访问点
通过单例模式创建的对象可以在整个应用程序中被访问,方便其他类使用。
2.3 数据共享和协调
单例模式可以确保多个对象共享相同的状态和数据,方便不同模块之间的协作。
2.4避免资源冲突
在多线程环境下,使用单例模式可以避免多个线程同时访问和修改同一资源的问题,提高程序的稳定性和可靠性。
2.5 控制实例化过程
通过单例模式可以对对象的实例化过程进行控制,保证符合业务需求和设计规范。
三、单例模式在项目中有哪些实际应用
3.1 线程池
线程池在项目中被广泛使用,一般使用单例模式来保证线程池的全局唯一性,以便在整个项目中共享线程池。
3.2 数据库连接池
在项目中使用数据库连接池来管理数据库连接,提高数据库的性能和资源利用率。数据库连接池往往采用单例模式来保证全局唯一性,避免多次创建和销毁数据库连接。
3.3 日志管理器
在项目中使用日志管理器来统一管理日志的输出和记录。日志管理器往往使用单例模式来保证全局唯一性,以便在整个项目中方便地使用和管理日志。
3.4 配置管理器
在项目中使用配置管理器来统一管理项目的配置信息。配置管理器往往使用单例模式来保证全局唯一性,方便在整个项目中获取和修改配置信息。
3.5 缓存管理器
在项目中使用缓存管理器来统一管理缓存数据。缓存管理器往往使用单例模式来保证全局唯一性,以便在整个项目中共享缓存数据。
3.6 窗口管理器
在GUI应用程序中使用窗口管理器来管理窗口的创建、销毁和切换等操作。窗口管理器往往使用单例模式来保证全局唯一性,方便在整个应用程序中管理窗口的状态。
四、单例模式的实现与讲解
4.1 故事背景
悟空和猪八戒一起前往蟠桃园,发现了许多美味的蟠桃。两人都非常喜欢吃蟠桃,于是开始争夺。悟空利用他的武力优势,想要把所有的蟠桃都夺走。而猪八戒则凭借他的速度优势,想要抢先吃到更多的蟠桃。这时,唐僧看出两人之间的争斗会导致不必要的麻烦,决定采用单例模式来解决这个问题。他告诉悟空和猪八戒,蟠桃园中的蟠桃树每年只能结一次果实,而且每次只有一颗蟠桃。他们只能通过团结合作,才能每人分到一部分的蟠桃。悟空和猪八戒被唐僧的建议所感动,决定放弃争夺,开始团结合作。他们一起控制自己的贪念,每次只摘下一颗蟠桃,然后平均分给自己和其他人。
4.2 模式实现
唐僧类实现单例模式
/// <summary>
/// 定义唐僧类
/// </summary>
public class TangSeng
{
// 唐僧实例
private static TangSeng instance;
// 私有的构造函数,防止外部实例化
private TangSeng() { }
/// <summary>
/// 获取唐僧实例的方法
/// </summary>
/// <returns></returns>
public static TangSeng GetInstance()
{
// 使用双重锁定确保线程安全
if (instance == null)
{
lock (typeof(TangSeng))
{
if (instance == null)
{
instance = new TangSeng();
}
}
}
return instance;
}
/// <summary>
/// 分享蟠桃的方法
/// </summary>
/// <param name="peachCount">蟠桃总数量</param>
/// <param name="memberCount">每个人分到的蟠桃数量</param>
public void SharePeach(int peachCount, int memberCount)
{
int eachPeachCount = peachCount / memberCount;
Console.WriteLine("每个人分到的蟠桃数量:" + eachPeachCount);
}
}
孙悟空类实现单例模式
/// <summary>
/// 定义孙悟空类
/// </summary>
public class SunWuKong
{
// 悟空实例
private static SunWuKong instance;
// 私有的构造函数,防止外部实例化
private SunWuKong() { }
// 获取悟空实例的方法
public static SunWuKong GetInstance()
{
// 使用双重锁定确保线程安全
if (instance == null)
{
lock (typeof(SunWuKong))
{
if (instance == null)
{
instance = new SunWuKong();
}
}
}
return instance;
}
// 争夺蟠桃的方法
public void GrabPeach(int peachCount)
{
Console.WriteLine("悟空争夺到的蟠桃数量:" + peachCount);
}
}
猪八戒类实现单例模式
/// <summary>
/// 定义猪八戒类
/// </summary>
public class ZhuBajie
{
// 猪八戒实例
private static ZhuBajie instance;
// 私有的构造函数,防止外部实例化
private ZhuBajie() { }
// 获取猪八戒实例的方法
public static ZhuBajie GetInstance()
{
// 使用双重锁定确保线程安全
if (instance == null)
{
lock (typeof(ZhuBajie))
{
if (instance == null)
{
instance = new ZhuBajie();
}
}
}
return instance;
}
// 争夺蟠桃的方法
public void GrabPeach(int peachCount)
{
Console.WriteLine("猪八戒争夺到的蟠桃数量:" + peachCount);
}
}
主函数调用
public static void Main(string[] args)
{
// 唐僧实例
TangSeng tangSeng = TangSeng.GetInstance();
// 悟空实例
SunWuKong wukong = SunWuKong.GetInstance();
// 猪八戒实例
ZhuBajie zhuBajie = ZhuBajie.GetInstance();
// 唐僧分享蟠桃
tangSeng.SharePeach(10, 3);
// 悟空争夺蟠桃
wukong.GrabPeach(5);
// 猪八戒争夺蟠桃
zhuBajie.GrabPeach(3);
}
控制台输出结果
4.3 模式讲解
在上述代码中,我们首先定义了 TangSeng
、SunWuKong
和 ZhuBajie
这三个类。它们都有一个私有的静态实例变量,用来存储唯一的实例。
接下来,我们定义了一个私有的构造函数,防止外部直接实例化这些类的对象。
然后,我们使用双重锁定来实现线程安全的实例获取方法,也就是 GetInstance
方法。在这个方法中,首先检查实例变量是否为空,如果是,则进入临界区代码块。在临界区内部,再次检查实例变量是否为空,以确保在多线程环境下只有一个线程创建实例。如果为空,我们就创建新的实例并将其赋值给实例变量。最后,返回实例变量。
在 TangSeng
类中,我们还定义了一个 SharePeach
方法来模拟唐僧分享蟠桃的场景。该方法接收两个参数,分别是蟠桃的总数和人数。根据总数和人数计算出每个人分到的蟠桃数量,并输出到控制台。
在 SunWuKong
类和 ZhuBajie
类中,我们也定义了类似的方法 GrabPeach
来模拟悟空和猪八戒争夺蟠桃的场景。这些方法接收一个参数,即争夺到的蟠桃数量,并将其输出到控制台。
在 Main
函数中,我们首先通过调用 TangSeng.GetInstance()
、SunWuKong.GetInstance()
和 ZhuBajie.GetInstance()
来获取唐僧、悟空和猪八戒的实例。
然后,我们分别调用唐僧的 SharePeach
方法,悟空的 GrabPeach
方法和猪八戒的 GrabPeach
方法来模拟故事中的场景。
执行程序时,控制台会输出每个人分到的蟠桃数量、悟空争夺到的蟠桃数量和猪八戒争夺到的蟠桃数量。
五、单例模式需要注意的地方
5.1 线程安全
在多线程环境下,确保只有一个实例被创建。可以采用双重检查锁定、静态初始化、枚举等方式来实现线程安全。
5.2 私有构造函数
通过将构造函数声明为私有,可以防止外部直接实例化对象,只能通过单例模式提供的获取实例的方法获取对象。
5.3 延迟加载
单例对象的初始化可以延迟到第一次使用时进行,而不是在程序启动时即创建实例。
5.4 序列化与反序列化
如果单例类需要支持序列化和反序列化,需要注意在反序列化时是否会创建出新的实例。可以通过实现 ISerializable
接口或者添加 readResolve()
方法来防止反序列化时创建新的实例。
5.5 全局访问点
单例模式提供了一个全局访问点,可以在任何地方获取到唯一的实例。但也需要注意是否滥用单例模式,因为全局访问点可能会导致代码耦合性增加。
5.6 单元测试
单例模式在进行单元测试时可能会存在一些困难,因为它们具有全局状态。可以通过模拟或替代单例对象来解决这个问题。
5.7 防止反射攻击
通过在私有构造函数中增加防止多次实例化的判断,可以防止通过反射机制创建新的实例。
5.8 生命周期管理
单例对象的生命周期通常与整个应用程序的生命周期相同,需要注意在适当的时候释放资源。