C#面向对象设计模式纵横谈 学习笔记2 Singleton单例模式

本文深入讲解了设计模式中的单例模式,包括其目的、应用场景及实现方式,对比了单线程与多线程环境下单例模式的不同实现,并探讨了模式的扩展。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一讲主要讲的是创建型模式中的单例模式

首先说明了模式的分类

从目的来看:

  • 创建型(Creational)模式:负责对象创建。
  • 结构型(Structural)模式:处理类与对象间的组合。
  • 行为型(Behavioral)模式:类与对象交互中的职责分配。

从范围来看:

  • 类模式处理类与子类的静态关系。
  • 对象模式处理对象间的动态关系。

在设计模式中为什么会存在单例模式?

在软件系统中,有时候需要一个类在系统中只存在一个实例,才能保证逻辑的正确性和良好的效率。

在Gof中是这样描述单例模式的意图保证一个类仅有一个实例,并提供一个该实例的全局访问点。

首先来看单线程的单例模式的实现方式

public class Singleton
{
    
private static Singleton instance;

    
private Singleton()
    
{

    }


    
public static Singleton Instance
    
{
        
get
        
{
            
if (instance == null)
            
{
                instance 
= new Singleton();
            }


            
return instance;
        }

    }

}

这是一个单例模式的例子。在这里,我们将默认构造函数设置私有的,如果我们不设置这个私有的构造函数,编译器将为我们创建一个默认的公开的默认构造函数,那么就可以在外部实例化,所以我们要将默认构造函数私有化。在Instance的属性中,我们判断instance是否为空,如果为空,那么就实例化,不为空,那么我们就直接返回,这要保证Instance属性被调用多次,返回的始终是一个实例。如果在c++中,instance是一个指针,并且需要在类外声明并初始化它。在.net中我们还可以这样来实现一个Singleton:

public class Singleton
{
    
private static Singleton instance = new Singleton();

    
private Singleton()
    
{

    }


    
public static Singleton Instance
    
{
        
get
        
{
            
return instance;
        }

    }

}

这种方式也实现了单例模式,但在c++中是不可行的。因为c++在不支持成员变量在声明时就初始化。

单线程单例模式的几个要点:

  • Singleton模式中的实例构造器可以设置为protected以允许子类派生。
  • Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
  • Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样与Singleton模式的初衷违背。
  • Singletom模式只考虑到了对象创建的管理,没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。因为只有一个实例。在C++中,我们随时可以销毁单例模式产生的一个实例。但是这样也带来了一个问题,就是我销毁以后,在调用GetInstance得到一个实例,这个实例与上一个实例,不是同一个实例了。所以最好是在程序结束运行时销毁单例。
  • 不能应对多线程环境:在多线程环境下,使用Singleton模式仍然有可能得到Singleton类的多个实例对象

多线程的单例模式:

public class Singleton
{
    
private static volatile Singleton instance;
    
private static object lockFlag = new object();
    
private Singleton()
    
{

    }


    
public static Singleton Instance
    
{
        
get
        
{
            
if (instance == null)
            
{
                
lock (lockFlag)
                
{
                    
if (instance == null)
                    
{
                        instance 
= new Singleton();
                    }

                }

            }


            
return instance;
        }

    }

}

volatile 关键字表示字段可能被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。

在代码中我们看到了检查了两次instance是否为空,这个是Double Check,为什么要进行Double Check?假设有两个线程同时调用单例模式的Instance属性,那么这时候都检测到instance为空,然后有一个线程A锁住标记,另一个线程B阻塞。如果在lock里没有再进行一次判断,当线程A释放锁,那么线程B将会得到一个新的Instance实例,这就与单例模式相冲突了。

多线程的单例模式在.net中还有一种实现方式

public class Singleton
{
    
public static readonly Singleton Instance = new Singleton();

    
private Singleton()
    
{

    }

}

 

它等同于下面的实现

public class Singleton
{
    
public static readonly Singleton Instance;

    
static Singleton()
    
{
        Instance 
= new Singleton();
    }


    
private Singleton()
    
{
    }

}

readonly关键字,表明了这个变量是初始化后是只读的,不可在更改。那么static构造函数保证了在使用类的静态字段的时候就被调用。不实用静态变量就不会调用static构造函数,并且staic构在函数保证了在多线程环境下只调用一次,不会被多次调用,这是.net的机制保证的。但是带readonly的单例模式不支持带参数构造函数,staic构造函数不支持参数并且也是私有的,外表无法显示调用。当然可以用变通的方法来解决这个问题。可以添加一个方法比如叫InitSington,带有参数,在的到单例的时候马上调用InitSingleton来初始化单例的内部成员。

Singleton的模式扩展

  • 将一个实例扩展到n个实例,例如对象池的实现。可以通过添加计数器来控制,或者提供一个方法,调用这个方法就实例化n个实例,然后再不会创建实例。
  • 将new 构造器的调用转移到其他类中,例如多个类协同工作环境中,某个局部环境只需要拥有某个类的一个实例。如.net中同一类型多个实例调用GetType()得到的Type类型是一个实例。我简单的模拟了一下实现方式
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Data;
    using System.Data.SqlClient;

    namespace ConsoleApplication3
    {
        
    class Program
        
    {
            
    static void Main(string[] args)
            
    {
                MyClassA a1 
    = new MyClassA();
                MyClassA a2 
    = new MyClassA();
                MyClassB b1 
    = new MyClassB();
                
    if (a1.GetMyType() == a2.GetMyType())
                
    {
                    Console.WriteLine(
    "a1 equals a2");
                }

                
    else
                
    {
                    Console.WriteLine(
    "a1 does not equal a2");
                }


                
    if (a1.GetMyType() != b1.GetMyType())
                
    {
                    Console.WriteLine(
    "a1 does not equal b1");
                }

                
    else
                
    {
                    Console.WriteLine(
    "a1 equals b1");
                }


                Console.WriteLine(a1.GetMyType().TypeName);
                Console.WriteLine(b1.GetMyType().TypeName);

                Console.Read();
            }

        }



       

        
    public class MyType
        
    {
            
    private string m_TypeName;

            
    public MyType(string typeName)
            
    {
                m_TypeName 
    = typeName;
            }


            
    public string TypeName
            
    {
                
    get
                
    {
                    
    return m_TypeName;
                }

            }

        }


        
    public class MyObject
        
    {
            
    private static Dictionary<string, MyType> myTyp = new Dictionary<string,MyType>();
            
    protected string MyTypeName;
            
    public MyType GetMyType()
            
    {
                
    if(myTyp.ContainsKey(MyTypeName))
                
    {
                    
    return myTyp[MyTypeName];
                }

                
    else
                
    {
                    myTyp.Add(MyTypeName, 
    new MyType(MyTypeName));
                }


                
    return myTyp[MyTypeName];
            }

        }


        
    public class MyClassA : MyObject
        
    {
            
    public MyClassA()
            
    {
                
    base.MyTypeName = "MyClassA";
            }

        }


        
    public class MyClassB : MyObject
        
    {
            
    public MyClassB()
            
    {
                
    base.MyTypeName = "MyClassB";
            }

        }

    }

          当然Type类的实现方式肯定跟我这个不一样,我这里只是简单的模拟

  • 理解和扩展Singleton模式的核心是"如何控制用户使用new对一个类的实例构造器的任意调用"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值