设计模式学习总结单例模式(Singleton Pattern)

问题:
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,并提只有一个全局访问点,才能确保它们的逻辑正确性、以及良好的效率。

定义:
单例模式也叫也叫单件模式是一种对象创建模式, 保证一个类仅有一个实例,并提供一个该实例全局的访问点。这个类称为单例类。

意图:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例.

参与者:
•单例类:
确保类只有一个实例,而且自行实例化并向整个系统提供这个实例。

UML图:

 
实例说明:

诺基亚手机工厂
诺基亚手机工厂,可以生产多款手机,可是手机工厂现在仅有一个,这里使用单例模式确保系统中仅有一个手机工厂,保证系统无论什么时候生产手机生产什么手机都使用此工程。

uml图:

 
代码:

/// <summary>
/// 手机接口
/// </summary>
public interface INokiaPhone
{
    string GetPhoneName();
}
/// <summary>
/// N8手机具体类
/// </summary>
public class N8Phone : INokiaPhone
{
    public string GetPhoneName()
    {
        return "我是N8";
    }
}
/// <summary>
/// N9手机具体类
/// </summary>
public class N9Phone : INokiaPhone
{
    public string GetPhoneName()
    {
        return "我是N9";
    }
}
/// <summary>
/// 手机生产工厂类
/// </summary>
public class PhoneFactory
{
    public INokiaPhone CreateNokiaPhone(string phoneName)
    {
        switch (phoneName)
        {
            case "N8":
                return new N8Phone();
            case "N9":
                return new N9Phone();
            default:
                return null;
        }
    }
}
/// <summary>
/// 诺基亚手机工厂单例
/// </summary>
public class SingletonPhoneFactory
{
    /// <summary>
    
/// 因为静态变量的生命周期跟整个应用程序的生命周期是一样的,所以可以定义一个私有的静态全局变量instance来保存该类的唯一实例
    
/// </summary>
    static SingletonPhoneFactory instance;
    /// <summary>
    
/// 手机工厂实例
    
/// </summary>
    PhoneFactory phoneFactory = null;
    /// <summary>
    
/// 单例类私有构造函数,用于初始化类
    
/// 该Singleton的构造函数必须是私有的,以保证客户程序不会通过new()操作产生一个实例,达到实现单例的目的
    
/// </summary>
    private SingletonPhoneFactory()
    {
        phoneFactory = new PhoneFactory();
    }
    /// <summary>
    
/// 供一个全局函数访问获得该实例,并且在该函数提供控制实例数量的功能
    
/// </summary>
    
/// <returns></returns>
    public static SingletonPhoneFactory GetInstance()
    {
        if (instance == null)
        {
            instance = new SingletonPhoneFactory();
        }
        return instance;
    }
    /// <summary>
    
///  类方法
    
/// </summary>
    
/// <param name="_phoneName"></param>
    
/// <returns></returns>
    public INokiaPhone CreateNokiaPhone(string _phoneName)
    {
        return phoneFactory.CreateNokiaPhone(_phoneName);
    }

}
/// <summary>
/// 客户端
/// </summary>
static void SingletonTest()
{
    SingletonPhoneFactory SingletonPhoneFactory = SingletonPhoneFactory.GetInstance();
    var n8 = SingletonPhoneFactory.CreateNokiaPhone("N8");
}

线程安全问题:

在多线程模式下当由两个线程同时调用的GetInstance()时一个线程进入 if 判断语句后但还没有实例化 Singleton 时,第二个线程到达,此时 singleton 还是为 null这样的话,两个线程均通过 if 语句的条件判断,然后调用了new Singleton()这样就无法保证它的唯一实例。

•使用线程锁的办法来解决多线程下单例模式的线程安全问题:

public class Singleton 
{
    private static Singleton instance; 
    private static object _lock = new object();
    private Singleton() 
    { }
    public static Singleton GetInstance() 
    {
        if (instance == null
        { 
            lock (_lock)
            { 
                if (instance == null
                {
                    instance = new Singleton();
                }
            } 
        } return instance;
    } 
}

这段代码是优化过的,有两个if(instance==null)做双重判断其目的是为了解决由线程同步带来的性能问题。 

每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重 singleton == null 的话,那么就只有在第一次,也就是 singleton ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。

•使用readonly静态初始化解决多线程单例线程安全问题

public sealed class Singleton

    private static readonly Singleton singleton = new Singleton(); 

    private Singleton() 
    { 
    } 

    public static Singleton GetInstance() 
    { 
        return singleton; 
    } 

代码中private static readonly Singleton singleton = new Singleton(); 因为是readonly的.net内部机制会保证此实例之初始化一次,所以这里无需显示地编写线程安全代码 。

静态类和单例模式区别

•单例可以继承类,实现接口,而静态类不能;

•单例可以被延迟初始化,静态类一般在第一次加载是初始化; 

•单例类可以被继承,他的方法可以被覆写;
•单例类可以被用于多态而无需强迫用户只假定唯一的实例。例如:如果有两个诺基亚手机工厂,两个工厂从同一个接口继承,每个工厂都是单例,通过返回接口实现多态。

•在静态方法中(类不是静态,方法是静态的)产生的对象,会随着静态方法执行完毕而释放掉,而且执行类中的静态方法时,不会实例化静态方法所在的类。如果是用singleton,   产生的那一个唯一的实例,会一直在内存中,不会被GC清除。 

应用情景:
•当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
•当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

 

原文地址:https://www.cnblogs.com/ejiyuan/p/2561166.html