单例模式在Java和C#中的实现

单例模式算是最常见和最容易理解一种设计模式了。通常是指某一个类只有一实例存在,存在的空间我认为可以理解为该类所在的应用系统内,还有一种是在某一个容器内单一存在,比如像spring的IOC容器(作用域为singleton的bean在容器内是单例存在的),也可以是个简单的HashMap。

单例模式的实现通常分两种,按习惯叫法是饿汉式和懒汉式,这两种的区别主要在于是否延迟初始化。以下是java的饿汉式单例实现:

public class SingletonDemo {
     //私有默认构造函数
      private SingletonDemo() {}
     //已经自行实例化 
     private static final SingletonDemo single = new SingletonDemo();

     public static SingletonDemo getInstance() {
         return single;
     }
 }

C#的实现与这个基本无异,单例的两个实现步骤是一私有化默认构造函数,使得类不可以在外部通过new操作实例化 (注:可以利用反射实例化),

二是内部自身实例化了一个对象供外部使用。那么取得一个SingletonDemo对象只能通过它的静态方法getInstance()了。我们再来看懒汉式的实现:

  public class SingletonDemo {

      private SingletonDemo() {}
      //注意这里没有final    
      private static SingletonDemo single=null;

      public synchronized static SingletonDemo getInstance() {
           if (single == null) {  
             single = new SingletonDemo();
         }  
        return single;
     }
 }
C#的实现:
public class SingletonDemo
{
       private static SingletonDemo instance;
       private static object _lock=new object();

       private SingletonDemo()
       { }

       public static SingletonDemo GetInstance()
       {
               if(instance==null)
               {
                      lock(_lock)
                      {
                             if(instance==null)
                             {
                                     instance=new SingletonDemo();
                             }
                      }
               }
               return instance;
       }
}

懒汉式主要在于使用时再实例化,可以说二者区别不大。另外懒汉式的一个缺点是要处理多线程调用而产生多个实例的问题,java使用了synchronized同步方法,而C#使用的是lock互斥锁。从这点上来说本人更喜欢饿汉式的简洁。

由上面我们已经知道了两种实现方式区别在于类成员的初始化顺序,我们看看java的成员初始化顺序:静态变量、静态初始化块)>(变量、初始化块)>构造器

很显然我们还可以在静态初始块中为single赋值

static final SingletonDemo single;

static{

single=new SingletonDemo();

//还可以干点其他事,比如启动一个hibernate的SessionFactory,哈哈

}

再看C#的,C#中是没有静态块这一说的,代替它的是静态函数

static readonly SingletonDemo single;

static SingletonDemo()

{

single=new SingletonDemo();

}

这里要提下的是有些人喜欢在静态块中做一些赋值或操作,NHibernate(.net版的hibernate)的示例有这么一段:

public class NHibernateHelper 
{
public static readonly Configuration _Configuration;
        private const string _CurrentSessionKey = "nhibernate.current_session";
        private static readonly ISessionFactory _SessionFactory;

        static NHibernateHelper()
        {
            log4net.Config.XmlConfigurator.Configure();
            _Configuration = new Configuration();
             _SessionFactory = _Configuration.Configure().BuildSessionFactory();
          
        }
}

当hibernate配置文件中的数据库配置存在错误时,这里将出现异常,而由于静态函数只在类初始化时运行一次,所以这个异常是不能弥补的,我们只能重启应用再试一次了。

我们也实现一个在容器内的单例,这回来个C#版的吧:

 public class DALFactory
    {
        private static Hashtable cacheDAL = new Hashtable();

        public static T createDAL<T>()
        {
            string CacheKey = typeof(T).FullName;//使用类全名作为key
            T dal = (T)cacheDAL[CacheKey];
            if (dal == null)
            {
                lock (cacheDAL)
                {
                    if (dal == null)
                    {
                        Type t = typeof(T);
                        dal = (T)Activator.CreateInstance(t);//反射实例化类
                        try
                        {
                            cacheDAL.Add(CacheKey, dal);
                        }
                        catch (ArgumentException) { }
                    }
                }
            }
            return dal;
        }
    }
思路很简单,一个键值对容器存放类的实例,使用类全名作为唯一键,要创建类实例时先从容器查找如果有则返回该对象,如果没有则新实例化一个并放入容器。