单例模式
单例模式 ( Singleton pattern ) 确保一个类只有一个实例,并提供全局访问点。在 Java 中实现单例模式需要私有的构造器、一个静态方法和一个静态变量。
一般在以下两种场景中会考虑使用单例模式:
产生某对象会消耗过多的资源,为避免频繁地创建与销毁对象对资源的浪费。如:对数据库的操作、访问 IO、线程池、网络请求等。
某种类型的对象应该有且只有一个。如果制造出多个这样的实例,可能导致:程序行为异常、资源使用过量、结果不一致等问题。比如一个系统只能有:一个窗口管理器或文件系统等。
单例模式按加载时机可以分为:懒汉式和饿汉式;按实现的方式则可以分为:双重检查锁,内部类方式和枚举方式等等。
懒汉式
1 | public class Singleton { |
优点:Lazy loading,第一次调用才初始化,避免了内存浪费。
缺点:只有第一次执行方法时才真正需要同步。即 uniqueSingleton 被初始化后的每次调用该方法,同步都是多余的,效率过低。非线程安全。
饿汉式
1 | public class Singleton { |
优点:没有加锁,而是基于 classloader 机制解决了多线程下的同步问题,执行效率会提高,线程安全。
缺点:不是 Lazy loading,无论方法是否被调用,类加载时就进行了初始化实例,如果该实例从始至终都没被使用过,会浪费内存。
双重检查锁 ( Double-checked locking )
1 |
|
优点:Lazy loading,采用双锁机制,线程安全且在多线程情况下能保持高性能。
静态内部类
1 | public class Singleton { |
这种方式与饿汉式一样,利用了 classloder 的机制来保证线程安全。但这种方式下 Singleton 类被装载了,INSTANCE 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。
优点:Lazy loading
枚举
不幸的是,以上四种方式都有一个致命的缺点——无法抵御序列化与反射攻击。Josh Bloch 在《Effective Java》中指出“单元素的枚举类型是实现单例模式的最佳方法”,它更简洁,自动支持序列化机制,绝对防止多次实例化,且能够抵御序列化与反射攻击。
1 | public enum Singleton { |
缺点:不是 Lazy loading
工厂模式
工厂模式 (Factory Pattern) 定义了一个创建对象的借口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
简单 / 静态工厂
简单工厂模式属于创建型模式,又叫做静态工厂方法 (Static Factory Method) 模式,不属于 23 种 GoF 设计模式。简单工厂模式由一个工厂对象决定创建出哪一种产品类的实例,是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
《Effective Java》中第一节就指出“使用静态工厂方法代替构造器”,并列举了静态工厂方法相较构造器的四大优势:有名称、对象可复用、可返回任何子类型对象、减少了创建参数化类型实例时的代码量。
但静态工厂方法也存在缺点:
类如果不含公有的或者受保护的构造器,就不能被子类化。
它们与其他的静态方法实际上没有任何区别。
1 | // 创建一个接口 |
当需求增加,不满足于之生产正方形、圆形,还需要三角形的时候,还需要:
添加一个三角形类,添加三角形类并不会影响到正方形类、圆形类。这符合开闭原则(扩展开放,修改关闭)。
在工厂类里面增加一个生产三角形类的方法,但这样需要在已有的类里面改动方法,并不符合“开闭原则”。
下面介绍符合“开闭原则”的工厂方法。
工厂方法
工厂方法模式是简单工厂模式的一种升级和抽象。工厂方法模式的核心逻辑是:通过将工厂类采用继承的方式细分减轻在简单模式中工厂类的压力。通过不同的工厂来分别管理不同的产品,一种类似树形的划分方式,让代码更加有可读性。
这样看似麻烦了些,但是每增加一个具体的产品并不需要对原来的工厂加以改进而是新增一个工厂,不会影响原有的工厂,符合“开闭原则”(拓展开放、修改关闭)
1 | // 创建一个接口 |
抽象工厂
如果还不满足于生产形状,还需要增加生产颜色产品时,可以引入抽象工厂。
抽象工厂为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。这一组相关或者相互依赖的对象有一个名词来形容——产品族。
1 | // 创建一个接口 |
反射实现简单工厂
利用反射实现简单工厂,不需要改动工厂类内的代码,同样实现了“开闭原则”。
1 | // 创建一个接口 |
代理模式
代理模式( Proxy pattern )为某个对象提供一个替身(代理对象),并由代理对象控制对原对象的引用。
代理模式可分为静态代理与动态代理,动态代理又可分为JDK动态代理与CGLib动态代理。
动态代理和静态代理的区别:
静态代理在编译时就已经实现,编译完成后代理类class文件就已被创建。
动态代理是在程序运行时通过反射机制动态生成的,编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。
静态代理
1 | // 创建一个接口 |
1 | // 创建一个实现该接口的类 |
1 | // 代理类 |
1 | // 测试 |