5种创建型模式:单例、工厂、抽象工厂、建造者、原型。
单例模式 Singleton
类图
使用场景
- 要求生成唯一序列号的环境。
- 整个项目需要一个共享访问点或共享数据。例如web页面上的计数器,使用单例保持计数器的值。
- 创建对象需要消耗过多资源。例如访问IO和数据库等。
- 需要定义大量静态常量和静态方法(如工具类)的环境(也可直接声明为 static 的方式)。
优点
- 内存中只有一个实例,减少了内存开销。避免对象频繁的创建、销毁。
- 减少系统性能开销。可在应用启动时之间产生单例对象去读取配置,产生其他依赖对象等。
- 避免对资源的多重占用。例如写文件只由单例去完成。
- 设置全局访问点,优化和共享资源访问。例如由单例类负责所有数据表的映射处理。
缺点
- 一般没有接口,扩展困难。提供单一实例、接口或抽象类。
- 对测试不利。单例没有完成没法测试,业不能 mock 一个虚拟对象。
- 与单一职责原则由冲突。一个雷实现一个逻辑,不关心是否单例。是不是单例取决于环境,单例模式把要单例 和 业务逻辑 融合在一个类中。
注意事项
线程同步问题
高并发时,可能会出现该问题。解决办法:饿汉、懒汉、二次检查等。
对象的复制情况
如果实现了 Cloneable 接口,即使私有构造函数对象仍然可以被复制。因为克隆不需要调用类的构造函数。对于单例类最好不要实现 Cloneable 接口。
扩展
有上限的单例模式**
决定内存中有多少个实例,修正单例可能存在的性能问题,提供系统的响应速度,例如读取文件在系统启动时完成初始化工作,在内存中启动固定数量的 reader 实例,然后在需要读取文件时就可以快速响应。
代码演示
1 | /** |
工厂方法模式 Factory
类图
使用场景
- 需要灵活可扩展的框架时。比如需要设计一个链接邮件服务器的框架,有3种网络协议可选:POP3、IMAP、HTTP,作为产品类,定义接口IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(连接方式),再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。
- 用在异构项目中。例如通过webservice与非java项目交互,虽然webservice号称可以做到异构系统的同构化,但实际会遇到类型问题、WSDL文件的支持问题等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
- 使用在测试驱动开发的框架下。例如测试类A,把与A有关联的B虚拟出来,避免A与B的耦合。该功能由于 JMock 和 EasyMock 的诞生使用场景已经弱化了。
优点
- 封装良好,代码结构清晰。只需知道产品类名或约束字符串,屏蔽创建过程,降低模块间的耦合。
- 优秀的扩展性。只需增加扩展类,无需修改工厂类。
- 屏蔽产品类。接口不变系统中的上层模块就不要发生变化。例如 JDBC 连接数据库,切换数据库需要改动的只是驱动名称。
- 解耦框架。高层模块只需要指导产品的抽象类,符合迪米特法则(只关心需要关心的)、依赖倒置原则(只依赖产品类的抽象)、里氏替换原则(使用产品子类替换父类)。
扩展
缩小为简单工厂模式
升级为多个工厂类
代替单例模式
延迟初始化
通过map缓存需要重用的对象
代码演示
简单工厂模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53public class SimpleFactory {
public Car getCar(String name) {
if ("bmw".equalsIgnoreCase(name)) {
return new Bmw();
} else if ("benz".equalsIgnoreCase(name)) {
return new Benz();
} else {
System.out.println("暂时不生产这个品牌的汽车");
return null;
}
}
/**
* 静态方法通过反射创建产品类
*/
public static <T extends Car> T getCar(Class<T> c) {
Car car = null;
try {
car = (Car) Class.forName(c.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return (T) car;
}
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Car car = simpleFactory.getCar("bmw");
System.out.println(car.getName());
}
}
/**
* 产品类
*/
interface Car {
String getName();
}
class Benz implements Car {
public String getName() {
return "奔驰";
}
}
class Bmw implements Car {
public String getName() {
return "宝马";
}
}工厂模式
不能存在一个工厂生产所有产品的情况,所以需要多个工厂进行不同产品的生产。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* 工厂类
* 多个工厂,封装不同的产品生产流程
*/
public interface Factory {
Car getCar();
}
class BenzFactory implements Factory {
public Car getCar() {
return new Benz();
}
}
class BmwFactory implements Factory {
public Car getCar() {
return new Bmw();
}
}
class FactoryTest {
public static void main(String[] args) {
BenzFactory benzFactory = new BenzFactory();
System.out.println(benzFactory.getCar().getName());
BmwFactory bmwFactory = new BmwFactory();
System.out.println(bmwFactory.getCar().getName());
}
}替代单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class SingletonFactory {
private static Singleton instance;
static {
try {
Class c = Class.forName(Singleton.class.getName());
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
instance = (Singleton) constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Singleton getInstance() { return instance; }
}
class Singleton {}延迟初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class MapFactory {
private static final Map<String, Product> PRODUCT_MAP = new HashMap<>();
public static synchronized <T extends Product> T getProduct(Class<T> c) throws Exception {
T product = null;
if (PRODUCT_MAP.containsKey(c.getName())) {
product = (T) PRODUCT_MAP.get(c.getName());
} else {
T t = c.newInstance();
PRODUCT_MAP.put(c.getName(), t);
}
return product;
}
}
class Product {}
class Product1 extends Product {}
抽象工厂模式 Abstract Factory
类图
使用场景
- 一个对象族(或是一组没有任何关系的对象)都有相同的约束,就可以使用抽象工厂模式。比如文本编辑器和图片处理器都是软件,但是 linux 和 windows 下文本编辑器虽然界面相同,但是代码不同,也就有了共同的约束条件——操作系统。
优点
- 封装性好。高层模块不关心每个产品的实现类,值关心接口。对象的创建由工厂类负责。高层模块只需要知道工厂类有哪些就行。
- 产品族内的约束为非公开状态。例如下面代码中约定每生产1个发动机,需要设计出3种外形与之匹配。
缺点
- 产品族扩展非常困难。如果新增一个产品,需要修改抽象工厂类、几个实现类,严重违反开闭原则。
注意事项
与工厂模式的区别
工厂模式用来创建同一个产品的不同类型,如汽车里的奔驰、宝马,抽象工厂模式用来创建不同类的产品,汽车工厂设计汽车造型,生产发动机等。一般来说产品种类单一适合用工厂模式,如果有多个种类各种类型,更适合用抽象工厂模式。
产品族扩展困难,产品等级扩展容易
也就是横向扩展容易,纵向扩展困难。例如下面代码新增车型奥迪,只需要新增奥迪工厂类即可。从这一点上,抽象工厂符合开闭原则。
代码演示
1 | // 抽象工厂 |
建造者模式 Builder
类图
使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果。
- 多个部件或零件都可以装配到一个对象中,但是产生的运行结果又不相同。
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能。
- 对象创建过程中会使用到系统中的一些其他对象,这些对象在产品创建过程中不易得到时,可采用建造者模式封装该对象的创建过程。这种场景只能是一个补偿方法,因为一个对象不容易获得,在设计阶段竟然没发觉,而要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
- 当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性差,可用 builder 模式重构。如 lombok 的 @Builder 注解。
优点
- 封装性好。使客户端不必知道产品内部组成的细节,只关注产生的对象和要素。
- 建造者独立,更容易扩展。多个 builder 相互独立,对系统的扩展非常有利。
- 便于控制细节风险。具体建造者独立,因此可以对建造过程逐步细化,而不对其他模块产生任何影响。
注意事项
与工厂模式的区别
建造者模式最主要的功能是方法的调用顺序安排,通俗讲就是零件的装配,顺序不同对象也不同。即关注的是零件类型和装配工艺(顺序)。
工厂方法主要职责是创建零件,而不关心组装顺序。
扩展
- 与模板方法模式进行结合
代码演示
1 | public abstract class Builder { |
原型模式 Prototype
使用场景
- 资源优化场景。类初始化消耗较多资源,包括数据、硬件等。
- 性能和安全要求的场景。避免通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
- 一个对象多个修改者的场景。拷贝多个对象供各个调用者使用。
- 原型模式一般与工厂方法模式一起使用,通过 clone 方法创建一个对象,然后由工厂方法提供给调用者。
优点
- 性能优良。内存二进制流的拷贝,比直接 new 一个对象性能好很多,特别是在循环体内产生大量对象。
- 逃避构造函数的约束。直接内存拷贝,不会执行构造函数,即使是 private 也可以克隆。双刃剑。
注意事项
构造函数不会被执行
深拷贝和浅拷贝
使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二必须时一个可变得引用对象,而不是原始类型或不可变对象。
深拷贝和浅拷贝建议不要混合使用,特别是涉及类的继承时,父类有多个引用的情况就非常复杂,建议深拷贝和浅拷贝分开实现。
clone 与 final 有冲突。要使用 clone,类成员变量上就不能加 final 修饰。
代码演示
构造函数不执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Prototype implements Cloneable {
public Prototype() {
System.out.println("构造函数被执行。。");
}
protected Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Prototype prototype = new Prototype();
Prototype clone = prototype.clone();
}
}深拷贝与浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47// 浅拷贝
class LightClone implements Cloneable {
private List<String> list = new ArrayList<>();
protected LightClone clone() throws CloneNotSupportedException {
return (LightClone) super.clone();
}
public void addValue(String value) { list.add(value); }
public List<String> getValueList() { return this.list; }
}
class Client1 {
public static void main(String[] args) throws CloneNotSupportedException {
LightClone origin = new LightClone();
origin.addValue("111");
LightClone clone = origin.clone();
clone.addValue("222");
System.out.println(origin.getValueList());
System.out.println(clone.getValueList());
System.out.println(origin.getValueList() == clone.getValueList());
}
}
// 深拷贝
class DeepClone implements Cloneable {
private ArrayList<String> list = new ArrayList<>();
protected DeepClone clone() throws CloneNotSupportedException {
DeepClone clone = (DeepClone) super.clone();
clone.list = (ArrayList<String>) clone.list.clone();
return clone;
}
public void addValue(String value) { list.add(value); }
public List<String> getValueList() { return this.list; }
}
class Client2 {
public static void main(String[] args) throws CloneNotSupportedException {
DeepClone origin = new DeepClone();
origin.addValue("111");
DeepClone clone = origin.clone();
clone.addValue("222");
System.out.println(origin.getValueList());
System.out.println(clone.getValueList());
System.out.println(origin.getValueList() == clone.getValueList());
}
}增加 final 的拷贝
1
2
3
4
5
6
7
8
9
10
11public class FinalClone implements Cloneable {
private final ArrayList<String> list = new ArrayList<>();
protected FinalClone clone() throws CloneNotSupportedException {
FinalClone clone = (FinalClone) super.clone();
// 下面这行代码报错
// clone.list = (ArrayList<String>) this.list.clone();
return clone;
}
}