单体模式(Singleton)备忘录

1.单体模式的宗旨
     单体模式的宗旨在于确保某个类只有唯一的一个实例,并且为该类提供一个全局的访问点。
     当我们开发项目时,经常会发现一些类需要只有一个实例,比如:Window Manager,Print Spooler,对数据库引擎的唯一访问点等等。单体模式被GoF归于创建型的模式,但我认为它应归位责任型的模式,因为单体模式的价值在于将一个类的职责集中在唯一的一个单体对象之中。

2.单体模式的实现
     单体模式的实现有很多种,每种都有自身的优点和缺点,但是宗旨都是确保单体的唯一。由于static关键字的“天生资质”,注定了它与单体模式的“姻缘”。下面罗列出几种实现方法,假设的场景都是:将PrintSpooler类设计为单体,现在就让我们一起来比较一下吧!
     a.通过Exception机制
     首先为了程序更加清晰,我们自己定义一个异常类SingletonException:

  1. public class SingletonException extends RuntimeException {   
  2.       public SingletonException() {   
  3.             super();   
  4.       }   
  5.       public SingletonException(String s) {   
  6.             super(s);   
  7.       }   
  8. }     

      接下来就是单体模式关键的部分:

  1. public class PrintSpooler {   
  2.       static boolean instance_flag=false//true if 1 instance   
  3.       public PrintSpooler() throws SingletonException {   
  4.             if (instance_flag)   
  5.                   throw new SingletonException("Only one spooler allowed");   
  6.            else  
  7.                   instance_flag = true//set flag for 1 instance   
  8.                   System.out.println("spooler opened");   
  9.       }   
  10.       public void finalize() {   
  11.             instance_flag = false//clear if destroyed   
  12.       }   
  13. }  

       现在为止,PrintSpooler单体设计已经完成,让我们看看如何使用它:

  1. public class singleSpooler {   
  2.       static public void main(String argv[]) {   
  3.             PrintSpooler pr1, pr2;   
  4.             System.out.println("Opening one spooler");   
  5.             try {   
  6.                   pr1 = new PrintSpooler();   
  7.             } catch (SingletonException e) {   
  8.                   System.out.println(e.getMessage());   
  9.             }   
  10.             System.out.println("Opening two spoolers");   
  11.             try {   
  12.                   pr2 = new PrintSpooler();   
  13.             } catch (SingletonException e) {   
  14.                   System.out.println(e.getMessage());   
  15.            }   
  16.       }   
  17.  }   

       运行结果如下:
           Opening one spooler
           printer opened
           Opening two spoolers
           Only one spooler allowed
     通过结果我们可以很清楚的发现我们程序完成了设计的意图确保了实例的唯一,但是这种方法的缺点就是我们必须把调用代码包在try-catch块之中,使简单的调用变得太复杂了,对于具有“懒惰”美德的程序员来说,这样有一些太笨了......

      b.通过final和static
      看看下面的解决方案:

  1. final class PrintSpooler {   
  2.       static public void print(String s) {   
  3.             System.out.println(s);   
  4.        }   
  5. }   

      如何使用:

  1. public class staticPrint {   
  2.       public static void main(String argv[]) {   
  3.             Printer.print("here it is");   
  4.       }   
  5. }    


      将PrintSpooler设计为final约束的,这样它就不能被其他类所扩张(继承),通过static的特性确保内存中实例的唯一,直接通过类名调用方法。不过这看上去更像一个工具类了啊,不错,Java的Math库正是采取这种设计的,当然这种设计的缺点就是我们对PrintSpooler的约束过于强大了。
 
     c.GoF的解决方案
     下面让我们看看最流行的解决方案吧,当然也是我们在项目中用到的最广泛的GoF的那本“设计模式圣经”里面所描述的:

  1. public class PrintSpooler {     
  2.       private PrintSpooler(){}      
  3.    private static PrintSpooler instance = new PrintSpooler();      
  4.       public static PrintSpooler getInstance() {       
  5.             return instance;      
  6.       }   
  7. }    

     神奇吗?把构造函数设为private的,这样的话外部就不能实例化PrintSpooler类,只有自己本身能够实例化自己,然后通过静态方法getInstance()返回这个实例从而提供唯一的访问点。如果你试着运行这行代码:
             PrintSpooler p = new PrintSpooler();
     ^_^,编译器是不会放过你的!那么我们如何使用PrintSpooler呢?很简单,我们敲下这行代码就可以了:
             PrintSpooler p = PrintSpooler.getInstance();
    这个解决方案还有另外一种变形,就是用到Lazy-initialize:

  1. public class PrintSpooler {     
  2.       private PrintSpooler(){}      
  3.    private static PrintSpooler instance = null;      
  4.       public static synchronized PrintSpooler getInstance() {       
  5.              if(instance == null)   
  6.              instance = new PrintSpooler();    
  7.              return instance;      
  8.       }   
  9. }    

     这里有两个问题需要解决:为什么需要 Lazy-initialize?它是什么 ?
                                                     那里冒出的 synchronized ?
     首先,因为有时候单体类在实例化时所需的数据只有在运行时通过计算才能获得,所以我们必须延迟实例化,所以也就有了先前的惰性初始化(Lazy-initialize)--第一次调用时进行初始化。
     其次,考虑的线程安全的因素,如果一个线程发现instance==null而去实例化PrintSpooler,且此时PrintSpooler还未实例化完毕。与此同时另一线程也检查instance,结果为instance==null,所以也去实例化PrintSpooler了,现在PrintSpooler有了两个实例,不再是单体了。为了防止上面的情况发生,可以用synchronized提供的锁机制,所以就冒出了synchronized!

3.需要注意的问题
     有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器和JVM的。所以在EJB中,单体模式就失效了!

4.再罗嗦一些......
     在工厂模式中的类装载器(Class Loader)就是一个单体,J2EE设计模式中的ServiceLocator也是一个单体...我们仔细观察会发现我们的项目中在不知不觉用到好多的单体,其实单体模式的实现并没有难点,真正的难点在于在系统中如何识别单体和不滥用单体,这种敏感性只有不断的实践中才能锻炼出来,所以用好Singleton是非常不容易的事情......

你可能感兴趣的:(设计模式,jvm,ejb)