软件设计模式之单例模式

单例模式的概念:

单例模式保证一个类只有一个实例,并且提供一个访问它的全局访问点;

单例模式存在的意义:

单例模式用于当我们系统中的某个对象只需要一个实例的情况;例如PC远程桌面只能有一个人进行操作一个用户;

实现思路:

(1)、需要确保一个类只有一个实例

    创建公有类、私有构造函数实现;

    

1
2
3
4
5
6
7
8
9
public class Singleton
 {
     //私有变量来记录Singleton的唯一实例
     private static Singleton singleton;
     //私有构造函数
     private Singleton()
     {
     }
 }   

  

(2)、提供一个访问他的全局访问点

  (2.1)、加锁

  (2.2)、双重锁定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//定义共有方法来提供该类的唯一全局访问点
public static Singleton GetInstance()
{
    /*当第一个线程运行到这里时,此时会对locker对象“加锁”
     * 当第二个线程运行该方法时,首先检测到locker对象为“加锁状态”,该线程就会挂       
      起等待第一个线程解锁
     * lock语句运行完之后(即线程运行完之后)会对该对象解锁
     * 双重锁定只需要一句判断就可以
     * **/
    if (singleton == null)
    {
        lock (locker)
        {
            //判断该实例是否存在,不存在则new一个新实例,否则返回已有实例
            if (singleton == null)
            {
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}         

 

总结:

公有类、私有构造函数、全局访问点(判断、加锁、判断)

 

完整源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Singleton
 {
     //私有变量来记录Singleton的唯一实例
     private static Singleton singleton;
 
     //定义一个标识确保线程同步
     private static readonly object locker = new object();
 
     //私有构造函数
     private Singleton()
     {
     }<br><br>        public static Singleton GetInstance()
     {
         /*当第一个线程运行到这里时,此时会对locker对象“加锁”
          * 当第二个线程运行该方法时,首先检测到locker对象为“加锁状态”,该线程就会挂起等待第一个线程解锁
          * lock语句运行完之后(即线程运行完之后)会对该对象解锁
          * 双重锁定只需要一句判断就可以
          * **/
         if (singleton == null)
         {
             lock (locker)
             {
                 //判断该实例是否存在,不存在则new一个新实例,否则返回已有实例
                 if (singleton == null)
                 {
                     singleton = new Singleton();
                 }
             }
         }
         return singleton;
     }
 } 

      /// <summary>
      /// 定义共有方法来提供该类的唯一全局访问点
      /// </summary>
      /// <returns>实例化对象</returns>

 

 

测试:

OK,实现已经结束了,现在来测试下单例模式的效果;

首先在单例模式类中定义一个测试用的字符串

1
2
3
4
//测试单例模式效果
private string strTest;
 
public string StrTest { get => strTest; set => strTest = value; }

然后设计测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
    Singleton singleton = Singleton.GetInstance();
    singleton.StrTest = "test";
    Console.WriteLine(singleton.StrTest);
    TestSingleton();
}
 
private static void TestSingleton()
{
    Singleton singleton = Singleton.GetInstance();
    Console.WriteLine(singleton.StrTest);
}

  

按照非单例模式上列代码执行结果应该是先显示test,然后显示空串

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args)
{
    Test test = new Test();
    test.StrTest = "test";
    Console.WriteLine(test.StrTest);<br>       TestSingleton();
}
 
private static void TestSingleton()
{
    Test test = new Test();
    Console.WriteLine(test.StrTest);
}

  

测试结果:

 

运行单例模式测试代码测试结果如下:

 

根据上面测试的结果可以知道,无论是重新多少次调用入口,它永远只会让你访问那一个实例!


 

其他写法:

饿汉模式:

1
2
3
4
5
public class DeviceSearchReply//公开类
{
        public readonly static DeviceSearchReply Instance = new DeviceSearchReply();//对外开放静态唯一实例化方式
        private DeviceSearchReply() { }//私有构造函数
}   

  优点:代码简约,无需自己考虑线程安全性问题,CLR会帮忙处理,

 

使用.NET4的Lazy类型实现的单例模式:

上面第一种单例模式的缺点:

1、第一种单例模式它在JAVA中是不起作用的,通常来说C#程序员也很有可能同时是名JAVA程序员。

JAVA内存模型不能保证构造函数在对新对象的引用分配给Instance之前完成,在没有volatile变量的情况下,双重检查锁定依然会被破坏。

2、在没有任何内存障碍的情况下,ECMA CLI规范也打破了这一限制。有可能在.NET 2.0内存模型(比ECMA规范更强)下它是安全的,但我宁愿不依赖那些更强大的语义,特别是如果对安全性有任何疑问的话。使instance变量volatile变得有效,就像明确的内存屏障调用一样,尽管在后一种情况下,甚至专家也无法准确地就需要哪些屏障达成一致。我尽量避免专家对对错意见也不一致的情况!

3、很容易出错。该模式需要完全如上所述——任何重大变化都可能影响性能或正确性。

4、性能较差。

饿汉模式缺点:

1、它并不像其他实现那样懒惰。特别是,如果您有Instance之外的静态成员,那么对这些成员的第一次引用将涉及到创建实例。

2、如果一个静态构造函数调用另一个静态构造函数,而另一个静态构造函数再次调用第一个构造函数,则会出现复杂情况。查看.NET规范(目前是分区II的第9.5.3节),了解有关类型初始化器的确切性质的更多详细信息——它们不太可能会影响您,但是有必要了解静态构造函数在循环中相互引用的后果。

3、类型初始化器的懒惰性只有在.NET没有使用名为BeforeFieldInit的特殊标志标记类型时才能得到保证。不幸的是,C#编译器(至少在.NET 1.1运行时中提供)将所有没有静态构造函数的类型(即看起来像构造函数但被标记为静态的块)标记为BeforeFieldInit。另请注意,它会影响性能。

1
<br><br><br><strong>Lazy类型实现的单例模式暂时不知道它有啥缺点</strong>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// <summary>
/// 使用.NET 4的 Lazy 类型实现的单例模式
/// </summary>
public sealed class BaseConfiguration//公开的类及sealed标识;sealed:阻止其他类继承此类
{
    private BaseConfiguration(){ }//私有的构造函数,不允许直接实例化
 
    /* Lazy:延迟初始化
     * 属性:
     * IsValueCreated:获取一个值,该值表示是否为该 Lazy<T> 实例创建了值
     * value:获取当前 Lazy<T> 实例的延迟初始化值。
     *
     * 描述:
     * 默认情况下,类的所有公共和受保护成员 Lazy<T> 都是线程安全的,可从多个线程并发使用。
     * 使用类型的构造函数的参数时,可以根据需要删除和每个线程安全保证
     *
     *
     * 搭建一个私有静态只读的Lazy对象,表明承载的对象为BaseConfiguration 并在()中使用拉姆达表达式进行实例化
     * **/
    private static readonly Lazy<BaseConfiguration> lazy = new Lazy<BaseConfiguration>(()=>new BaseConfiguration());
 
    /// <summary>
    /// 对外提供的唯一获取实例的构造函数
    /// </summary>
    public static BaseConfiguration GetBaseConfiguration
    {
        get {
            return lazy.Value;
        }
    }
 
 
}

  

posted @   壹-ZL  阅读(341)  评论(2编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示