欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识

提供一个公有的静态工厂方法: // Singleton with static factorypublic class

点击: 次  来源:昆山软开发 时间:2018-08-01

原文出处: 拿笔小星_

最近在阅读《Effective Java 》这本书,第3个条款专门提到了单例属性,并给出了利用单例的最佳实践发起。让我对这个单例模式(原本我觉得是设计模式中最简朴的一种)有了更深的认识。

单例模式

单例模式(Singleton Pattern)是 Java 中最简朴的设计模式之一。这种范例的设计模式属于建设型模式,它提供了一种建设工具的最佳方法。

在应用这个模式时,单例工具的类必需担保只有一个实例存在。很多时候整个系统只需要拥有一个的全局工具,这样有利于我们协调系统整体的行为。

单例的特点

  1. 单例类只能有一个实例。
  2. 单例类必需本身建设本身的独一实例。
  3. 单例类必需给所有其他工具提供这一实例。

单例模式的7种写法

单例模式的写法许多,涉及到了线程安详和机能问题。在这里我不反复先容。这篇《单例模式的七种写法》写得很具体,博主也给出了每一种写法的优缺点。

可是,单例模式真的可以或许实现实例的独一性吗?谜底是否认的。

如何粉碎单例

反射

有两种常见的方法来实现单例。他们的做法都是将结构要领设为私有,并导出一个公有的静态成员来提供对独一实例的会见。在第1种方法中,成员是个final字段:

// Singleton with public final field
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public void leaveTheBuilding() { ... }
}

只挪用私有结构函数一次,以初始化民众静态final字段elvi.instance。不提供公有的可能受掩护的结构函数担保了全局独一性:当Elvis类初始化的时候,仅仅只会有一个Elvis实例存在——不多也不少 。无论客户端怎么做都无法改变这一点,只不外我照旧要告诫一下 :授权的客户端可以通过反射来挪用私有结构要领,昆山软件开发,借助于AccessibleObject.setAccessible要领即可做到 。假如需要防御这种进攻,请修改结构函数,使其在被要求建设第二个实例时抛出异常。

测试代码:

public class TestSingleton {

    /**
     * 通过反射粉碎单例
     */
    @Test
    public void testReflection() throws Exception {
        /**
         * 验证单例有效性
         */
        Elvis elvis1 = Elvis.INSTANCE;
        Elvis elvis2 = Elvis.INSTANCE;

        System.out.println("elvis1 == elvis2 ? ===>" + (elvis1 == elvis2));
        System.err.println("-----------------");

        /**
         * 反射挪用结构要领
         */
        Class clazz = Elvis.class;
        Constructor cons = clazz.getDeclaredConstructor(null); 
        cons.setAccessible(true);

        Elvis elvis3 = (Elvis) cons.newInstance(null);

        System.out.println("elvis1 == elvis3 ? ===> "
            + (elvis1 == elvis3));
    }
}

运行功效:

Elvis Constructor is invoked!
elvis1 == elvis2 ? ===> true
elvis1 == elvis3 ? ===> false
-----------------
Elvis Constructor is invoked!

结论:

反射是可以粉碎单例属性的。因为我们通过反射把它的结构函数设成可会见的,然后去生成一个新的工具。

改造版的单例写法:

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        System.err.println("Elvis Constructor is invoked!");
        if (INSTANCE != null) {
            System.err.println("实例已存在,无法初始化!");
            throw new UnsupportedOperationException("实例已存在,无法初始化!");
        }
    }

}

功效:

Elvis Constructor is invoked!
elvis1 == elvis2 ? ===> true
-----------------
Elvis Constructor is invoked!
实例已存在,昆山软件开发,无法初始化!

第2种实现单例模式的要领是,提供一个公有的静态工场要领:

// Singleton with static factory
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance() { return INSTANCE; }
    public void leaveTheBuilding() { ... }
}

所有挪用Elvis类的getInstance要领,返回沟通的工具引用,而且不会有其它的Elvis工具被建设。但同样有上面第1个要领提到的反射粉碎单例属性的问题存在。

序列化和反序列化

假如对上述2种方法实现的单例类举办序列化,反序列化获得的工具是否是同一个工具呢?谜底是否认的。
看下面的测试代码:
单例类:

public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        System.err.println("Elvis Constructor is invoked!");
    }

}