设计模式 单例模式

来源:互联网 发布:淘宝返利网站 编辑:程序博客网 时间:2024/06/08 17:39

1 场景问题

1.1 读取配置文件的内容

考虑这样一个应用,读取配置文件的内容。

很多应用项目,都有与应用相关的配置文件,这些配置文件多是由项目开发人员自定义的,在里面定义一些应用需要的参数数据。当然在实际的项目中,这种配置文件多采用xml格式的。也有采用properties格式的,毕竟使用Java来读取properties格式的配置文件比较简单。

现在要读取配置文件的内容,该如何实现呢?

1.2 不用模式的解决方案

有些朋友会想,要读取配置文件的内容,这也不是个什么困难的事情,直接读取文件的内容,然后把文件内容存放在相应的数据对象里面就可以了。真的这么简单吗?先实现看看吧。

为了示例简单,假设系统是采用的properties格式的配置文件

1.那么直接使用Java来读取配置文件,示例代码如下:

public class AppConfig {    /**     *  参数A     */    private String paramterA;    /**     *  参数B     */     private String paramterB;    public String getParamterA() {        return paramterA;    }    public String getParamterB() {        return paramterB;    }    /**     * 构造方法从配置文件中读取参数     */    public AppConfig() {        readConfig();    }    private void readConfig() {        InputStream in = null;        try {            in = AppConfig.class.getResourceAsStream("config.properties");            Properties p = new Properties();            p.load(in);            this.paramterA = p.getProperty("paramterA");            this.paramterB = p.getProperty("paramterB");        } catch (Exception e) {            System.out.println("装载配置文件出错了,具体堆栈信息如下:");              e.printStackTrace();         } finally {              try {                  in.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}
  1. 客户端调用
public static void main(String[] args) {    AppConfig ap = new AppConfig();    System.out.println(ap.getParamterA());    System.out.println(ap.getParamterB());}

1.3 有何问题

看看客户端使用这个类的地方,是通过new一个AppConfig的实例来得到一个操作配置文件内容的对象。如果在系统运行中,有很多地方都需要使用配置文件的内容,也就是很多地方都需要创建AppConfig这个对象的实例。

换句话说,在系统运行期间,系统中会存在很多个AppConfig的实例对象,这有什么问题吗?

当然有问题了,试想一下,每一个AppConfig实例对象,里面都封装着配置文件的内容,系统中有多个AppConfig实例对象,
也就是说系统中会同时存在多份配置文件的内容,这会严重浪费内存资源。

在一个系统运行期间,某个类只需要一个类实例就可以了,那么应该怎么实现呢?

2 解决方案

2.1 单例模式来解决

用来解决上述问题的一个合理的解决方案就是单例模式。那么什么是单例模式呢?

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类实例的创建工作,
然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方式。

2.2 单例模式示例代码

单例模式的实现又分为两种,一种称为懒汉式,一种称为饿汉式,其实就是在具体创建对象实例的处理上,有不同的实现方式。下面分别来看这两种实现方式的代码示例。

1.懒汉式实现,示例代码如下:

public class Singleton {    private static Singleton instance;    /**     * 私有化构造方法     */    private Singleton() {    }    /**     * 定义一个方法来为客户端提供类实例     */    public static synchronized Singleton getInstance() {        if (instance == null) {            return new Singleton();        }        return instance;    }    /** * 示意方法,单例可以有自己的操作 */     public void singletonOperation(){         //功能处理    }     /**      *  示意属性,单例可以有自己的属性 */     private String singletonData;     /** * 示意方法,让外部通过这些方法来访问属性的值      * * @return 属性的值 */     public String getSingletonData(){         return singletonData;     }}

2.饿汉式

public class Singleton {    private static Singleton instance = new Singleton();    /**     * 私有化构造方法     */    private Singleton() {    }    /**     * 定义一个方法来为客户端提供类实例     */    public static synchronized Singleton getInstance() {        return instance;    }    /** * 示意方法,单例可以有自己的操作 */     public void singletonOperation(){         //功能处理    }     /**      *  示意属性,单例可以有自己的属性 */     private String singletonData;     /** * 示意方法,让外部通过这些方法来访问属性的值      * * @return 属性的值 */     public String getSingletonData(){         return singletonData;     }}

两者的区别:
懒汉式是典型的时间换空间

也就是每次获取实例都会进行判断,看是否需要创建实例,费判断的时间,当然,如果一直没有人使用的话,那就不会创建实例,节约内存空间。

饿汉式是典型的空间换时间

当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

2.3使用单例模式重写示例

1.懒汉式

public class AppConfig {    private static AppConfig instance = null;    /**     *  参数A     */    private String paramterA;    /**     *  参数B     */     private String paramterB;    public String getParamterA() {        return paramterA;    }    public String getParamterB() {        return paramterB;    }    /**     * 构造方法私有化     */    private AppConfig() {        readConfig();    }    public static synchronized AppConfig getInstance() {        if (instance == null) {            return new AppConfig();        }        return instance;    }    public void readConfig() {        InputStream in = null;        try {            in = AppConfig.class.getResourceAsStream("config.properties");            Properties p = new Properties();            p.load(in);            this.paramterA = p.getProperty("paramterA");            this.paramterB = p.getProperty("paramterB");        } catch (Exception e) {            System.out.println("装载配置文件出错了,具体堆栈信息如下:");              e.printStackTrace();         } finally {              try {                  in.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

2.饿汉式

public class AppConfig {    private static AppConfig instance = new AppConfig();    /**     *  参数A     */    private String paramterA;    /**     *  参数B     */     private String paramterB;    public String getParamterA() {        return paramterA;    }    public String getParamterB() {        return paramterB;    }    /**     * 构造方法私有化     */    private AppConfig() {        readConfig();    }    public static AppConfig getInstance() {        return instance;    }    public void readConfig() {        InputStream in = null;        try {            in = AppConfig.class.getResourceAsStream("config.properties");            Properties p = new Properties();            p.load(in);            this.paramterA = p.getProperty("paramterA");            this.paramterB = p.getProperty("paramterB");        } catch (Exception e) {            System.out.println("装载配置文件出错了,具体堆栈信息如下:");              e.printStackTrace();         } finally {              try {                  in.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

3.客户端

public static void main(String[] args) {    System.out.println(AppConfig.getInstance().getParamterA());    System.out.println(AppConfig.getInstance().getParamterB());}

3.在Java中一种更好的单例实现方式

public class SingleTon {    private SingleTon() {    }    /**     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载     */    private static class SingleTonHandler {        /**         * 静态初始化器,由JVM来保证线程安全         */        private static SingleTon instance = new SingleTon();    }    public static SingleTon getInstance() {        return SingleTonHandler.instance;    }    /**     * 当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,     * 导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,     * 从而创建Singleton的实例,由于是静态的域,因此只会被虚拟机在装载类的时候初始化一次,     * 并由虚拟机来保证它的线程安全性。     */}

4.枚举的单例实现

按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现Singleton的最佳方法。

public enum Singleton {    instacne;     /**     * 示意方法,单例可以有自己的操作     */      public void singletonOperation(){          //功能处理      }  }

使用枚举来实现单实例控制,会更加简洁,而且无偿的提供了序列化的机制,
并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

5.何时选用单例模式

当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,
这些功能恰好是单例模式要解决的问题。