设计模式专题——这次我们把单例模式讲清楚
每天进步一点点,坚持下去,你总是会不一样的。加油!
1、开闭原则
开闭原则:对扩展开发,对修改关闭。
在代码层面而言就是在你有新的需求的时候,你应当增加新的对象来实现,而不是修改原来的对象。
2、里氏代换原则
里氏代换原则:任何基类出现的地方,子类一定可以出现。
我将它称为父子代换原则,这也是为什么Java里面老是用接口或者抽象来作为参数然后传入的是子类或者实现的原因。因为接口与抽象存在的地方,都可以用它的实现或者子类来替换。其实这样也就实现了开闭原则。
3、依赖倒转原则
依赖倒转原则:针对接口编程,依赖和抽象而不是具体类。
为什么要这样?因为这样的话就可以实现多样性。统一用接口“人”作为参数,统一调用“人”的speak()方法,传入的是中国人,日本日,美国人等不同的具体实例,那么speak()的表现形式就不同了,但是你又不需要修改调用代码,因为你是根据接口编程的。
4、接口隔离原则
接口隔离原则 :使用多个隔离的接口,降低接口的偶合。
简而言之就是单一责任原则:就一个类来说,它应当只做一件事情,只有一个引起它变化的原因。
5、迪米特法则
迪米特法则:一个实体应当尽量少的与其他实体之间发生相互作用。
这个也被称为最少知道原则。
6、合成复用原则
合成复用原则:尽量使用合成/聚合的方式,而不是使用继承。
单例模式:保证一个类只有一个实例,并且提供一个访问该全局访问点。
优点
1、提供了对唯一实例的受控访问;
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能;
3、允许可变数目的实例;
4、避免对共享资源的多重占用。
缺点
1、不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
2、没有抽象层,因此单例类的扩展有很大的困难。
类初始化时,会立即加载该对象,线程天生安全,调用效率高。
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
//私有构造
private Singletone() {}
//直接创建好
private static Singletone singletone = new Singletone();
public static Singletone getInstance() {
return singletone;
}
public static void main(String[] args) {
Singletone single1 = Singletone.getInstance();
Singletone single2 = Singletone.getInstance();
System.out.println(single1 == single2);
}
}
输出结果为: true
懒汉式
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
//私有构造
private Singletone() {}
//默认不初始化,使用的时候才去创建
private static Singletone singletone;
public static synchronized Singletone getInstance() {
if (singletone == null) {
singletone = new Singletone();
}
return singletone;
}
public static void main(String[] args) {
Singletone single1 = Singletone.getInstance();
Singletone single2 = Singletone.getInstance();
System.out.println(single1 == single2);
}
}
输出结果为:true
静态内部方式
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
//私有构造
private Singletone() {}
//静态内部类
private static class ClassInstance {
private static final Singletone instance = new Singletone();
}
public static Singletone getInstance() {
return ClassInstance.instance;
}
public static void main(String[] args) {
Singletone single1 = Singletone.getInstance();
Singletone single2 = Singletone.getInstance();
System.out.println(single1 == single2);
}
}
输出结果是:true
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
private enum SingleEnum {
INSTANCE;
private Singletone singletone;
SingleEnum() {
singletone = new Singletone();
}
public Singletone getInstance() {
return singletone;
}
}
public static void main(String[] args) {
Singletone single1 = SingleEnum.INSTANCE.getInstance();
Singletone single2 = SingleEnum.INSTANCE.getInstance();
System.out.println(single1 == single2);
}
}
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
private static Singletone singletone;
public static Singletone getInstance() {
if (singletone == null) {
synchronized (Singletone.class) {
if (singletone == null) {
singletone = new Singletone();
}
}
}
return singletone;
}
public static void main(String[] args) {
Singletone single1 = Singletone.getInstance();
Singletone single2 = Singletone.getInstance();
System.out.println(single1 == single2);
}
}
破环单例模式的三种方式:反射,序列化,克隆
我们用上面的双重检测锁方式演示一下反射创建实例
/**
* @description: 单例模式
* @author:aerCool
* @since:2020-03-07 23:16
* @version:v1.0.0
*/
public class Singletone {
private static Singletone singletone;
public static Singletone getInstance() {
if (singletone == null) {
synchronized (Singletone.class) {
if (singletone == null) {
singletone = new Singletone();
}
}
}
return singletone;
}
public static void main(String[] args) {
Singletone single1 = Singletone.getInstance();
System.out.println("single1 的hashcode : "+ single1.hashCode());
//反射创建实例
try {
Constructor<Singletone> constructor = Singletone.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singletone single2 = constructor.newInstance();
System.out.println("single2 的hashcode : "+ single2.hashCode());
}catch (Exception e) {
}
}
}
输出结果如下,可见被破坏了:
single1 的hashcode : 3798928
single2 的hashcode : 31958635
七、如何防止反射、克隆、序列化对单例模式的破环
反射破环(虽然构造方法已私有化,但通过反射机制使用newInstance()方法构造方法可以创建实例)
首先定义一个全局变量开关createFlag默认为true
当第一次创建实例时将其状态更改为false
克隆破环
重写clone(),直接返回单例对象
序列化破环
添加readResolve(),返回Object对象
@Override
protected Singleton clone() throws CloneNotSupportedException {
return singleton;
}
private Object readResolve() {
return singleton;
}
如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性能高于饿汉式。
如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类性能高于懒汉式。最好使用饿汉式 。