设计模式 创建型
Java设计模式学习
- 本文作者: MrBird
- 本文链接: http://mrbird.cc/Java设计模式.html
- 本文仅为个人学习整理转载
创建型模式
简单工厂模式
简单工厂模式严格意义上来说,并不属于设计模式中的一种,不过这里还是简单记录下。
定义:由一个工厂对象决定创建出哪一种类型实例。客户端只需传入工厂类的参数,无心关心创建过程。
优点:具体产品从客户端代码中抽离出来,解耦。
缺点:工厂类职责过重,增加新的类型时,得修改工程类得代码,违背开闭原则。
举例:新建Fruit水果抽象类,包含eat抽象方法:
public abstract class Fruit {
public abstract void eat();
}
其实现类Apple:
public class Apple extends Fruit{
@Override
public void eat() {
System.out.println("吃🍎");
}
}
新建创建Fruit的工厂类:
public class FruitFactory {
public Fruit produce(String name) {
if ("apple".equals(name)) {
return new Apple();
} else {
return null;
}
}
}
新建个客户端测试一下:
public class Application {
public static void main(String[] args) {
FruitFactory factory = new FruitFactory();
Fruit fruit = factory.produce("apple");
fruit.eat();
}
}
运行main方法,输出:
吃🍎
可以看到,客户端Application并未依赖具体的水果类型,只关心FruitFactory的入参,这就是客户端和具体产品解耦的体现,UML图如下:

工厂方法模式
为了解决简单工厂模式的缺点,诞生了工厂方法模式(Factory method pattern)。
定义:定义创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到了子类进行。
优点:
- 具体产品从客户端代码中抽离出来,解耦。
- 加入新的类型时,只需添加新的工厂方法(无需修改旧的工厂方法代码),符合开闭原则。
缺点:类的个数容易过多,增加复杂度。
举例:新建Fruit抽象类,包含eat抽象方法:
public abstract class Fruit {
public abstract void eat();
}
新建FruitFactory抽象工厂,定义produceFruit抽象方法:
public abstract class FruitFactory {
public abstract Fruit produceFruit();
}
新建Fruit的实现类,Apple:
public class Apple extends Fruit {
@Override
public void eat() {
System.out.println("吃🍎");
}
}
新建FruitFactory的实现类AppleFruitFactory,用于生产具体类型的水果 —— 苹果:
public class AppleFruitFactory extends FruitFactory{
@Override
public Fruit produceFruit() {
return new Apple();
}
}
新建客户端Application测试一波:
public class Application {
public static void main(String[] args) {
FruitFactory factory = new AppleFruitFactory();
Fruit fruit = factory.produceFruit();
fruit.eat();
}
}
运行main方法,输出如下:
吃🍎
现在要新增Banana类型的水果,只需要新增Banana类型的工厂类即可,无需修改现有的AppleFruitFactory代码,符合开闭原则。但是这种模式的缺点也显而易见,就是类的个数容易过多,增加复杂度。
上面例子UML图如下所示:

抽象工厂模式
抽象工厂模式(Abstract factory pattern)提供了一系列相关或者相互依赖的对象的接口,关键字是“一系列”。
优点:
- 具体产品从客户端代码中抽离出来,解耦。
- 将一个系列的产品族统一到一起创建。
缺点:拓展新的功能困难,需要修改抽象工厂的接口;
综上所述,抽象工厂模式适合那些功能相对固定的产品族的创建。
举例:新建水果抽象类Fruit,包含buy抽象方法:
public abstract class Fruit {
public abstract void buy();
}
新建价格抽象类Price,包含pay抽象方法:
public abstract class Price {
public abstract void pay();
}
新建水果创建工厂接口FruitFactory,包含获取水果和价格抽象方法(产品族的体现是,一组产品包含水果和对应的价格):
public interface FruitFactory {
Fruit getFruit();
Price getPrice();
}
接下来开始创建🍎这个“产品族”。新建Fruit实现类AppleFruit:
public class AppleFruit extends Fruit{
@Override
public void buy() {
System.out.println("购买🍎");
}
}
新建对应的苹果价格实现ApplePrice:
public class ApplePrice extends Price{
@Override
public void pay() {
System.out.println("🍎单价2元");
}
}
创建客户端Application,测试一波:
public class Application {
public static void main(String[] args) {
FruitFactory factory = new AppleFruitFactory();
factory.getFruit().buy();
factory.getPrice().pay();
}
}
输出如下:
购买🍎
🍎单价2元
客户端只需要通过创建AppleFruitFactory就可以获得苹果这个产品族的所有内容,包括苹果对象,苹果价格。要新建🍌的产品族,只需要实现FruitFactory、Price和Fruit接口即可。这种模式的缺点和工厂方法差不多,就是类的个数容易过多,增加复杂度。
上面例子UML图如下所示:

建造者模式
建造者模式也称为生成器模式(Builder Pattern),将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。简单来说就是,相同的过程可以创建不同的产品。
将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
简单来说就是,相同的过程可以创建不同的产品。
适用于:
- 一个对象有非常复杂的内部结构(很多属性)
- 想将复杂对象的创建和使用分离。
优点:
- 封装性好,创建和使用分离
- 拓展性好,建造类之间独立,一定程度上解耦。
缺点:
- 产生多余的Builder对象;
- 产品内部发生变化,建造者需要更改,成本较大。
举个例子:
新增商铺类Shop,包含名称,地点和类型属性:
public class Shop {
private String name;
private String location;
private String type;
@Override
public String toString() {
return "Shop{" +
"name='" + name + '\'' +
", location='" + location + '\'' +
", type='" + type + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
接着创建Shop抽象生成器ShopBuilder:
public abstract class ShopBuilder {
private String name;
private String location;
private String type;
public abstract void name(String name);
public abstract void location(String location);
public abstract void type(String type);
public abstract Shop build();
}
包含和Shop相同的属性及对应的抽象构建方法。
继续创建ShopBuilder的实现,水果店构造器FruitShopBuilder:
public class FruitShopBuilder extends ShopBuilder{
private Shop shop = new Shop();
@Override
public void name(String name) {
this.shop.setName(name);
}
@Override
public void location(String location) {
this.shop.setLocation(location);
}
@Override
public void type(String type) {
this.shop.setType(type);
}
@Override
public Shop build() {
return shop;
}
}
创建个经销商类Dealer,用于通过ShopBuilder构建具体的商店:
public class Dealer {
private ShopBuilder builder;
public void setBuilder(ShopBuilder builder) {
this.builder = builder;
}
public Shop build(String name, String location, String type) {
this.builder.name(name);
this.builder.location(location);
this.builder.type(type);
return builder.build();
}
}
创建个客户端Application测试一波:
public class Application {
public static void main(String[] args) {
ShopBuilder builder = new FruitShopBuilder();
Dealer dealer = new Dealer();
dealer.setBuilder(builder);
Shop shop = dealer.build("XX水果店", "福州市XX区XX街XX号", "水果经营");
System.out.println(shop);
}
}
输出如下:
Shop{name='XX水果店', location='福州市XX区XX街XX号', type='水果经营'}
这个例子是典型的建造者模式,UML图如下所示:

其实建造者模式更为常用的例子是下面这个:
创建一个店铺类Shop,Shop里包含构造该Shop的内部类:
public class Shop {
private String name;
private String location;
private String type;
public Shop(ShopBuilder builder) {
this.name = builder.name;
this.location = builder.location;
this.type = builder.type;
}
@Override
public String toString() {
return "Shop{" +
"name='" + name + '\'' +
", location='" + location + '\'' +
", type='" + type + '\'' +
'}';
}
public static class ShopBuilder {
private String name;
private String location;
private String type;
public ShopBuilder name(String name) {
this.name = name;
return this;
}
public ShopBuilder location(String location) {
this.location = location;
return this;
}
public ShopBuilder type(String type) {
this.type = type;
return this;
}
public Shop build() {
return new Shop(this);
}
}
}
在客户端构建Shop只需:
public class Application {
public static void main(String[] args) {
Shop shop = new Shop.ShopBuilder()
.name("XX水果店")
.location("福州市XX区XX街XX号")
.type("水果经营")
.build();
System.out.println(shop);
}
}
这种用法和Lombok的@Builder注解效果是一样的。
这个例子的UML图:

单例模式
单例模式目的是为了一个类只有一个实例。
优点:
- 内存中只有一个实例,减少了内存开销;
- 可以避免对资源的多重占用;
- 设置全局访问点,严格控制访问。
缺点:
- 没有接口,拓展困难。
懒汉模式
懒汉模式下的单例写法是最简单的,但它是线程不安全的:
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
可加同步锁解决线程安全问题:
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
synchronized (LazySingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
}
但是同步锁锁的是整个类,比较消耗资源,并且即使运行内存中已经存在LazySingleton,调用其getInstance还是会上锁,所以这种写法也不是很好。
双重同步锁单例模式
public class LazyDoubleCheckSingleton {
private static LazyDoubleCheckSingleton instance = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
上面例子虽然加了同步锁,但它还是线程不安全的。虽然上面的例子不会出现多次初始化LazyDoubleCheckSingleton实例的情况,但是由于指令重排的原因,某些线程可能会获取到空对象,后续对该对象的操作将触发空指针异常。
要修复这个问题,只需要阻止指令重排即可,所以可以给instance属性加上volatile关键字:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
相关博文:深入理解volatile关键字。
上面这种写法是不但确保了线程安全,并且当LazyDoubleCheckSingleton实例创建好后,后续再调用其getInstance方法不会上锁。
静态内部类单例模式
看例子:
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
private static class InnerClass {
private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.instance;
}
}
为什么这个例子是可行的呢?主要有两个原因:
- JVM在类的初始化阶段会加Class对象初始化同步锁,同步多个线程对该类的初始化操作;
- 静态内部类InnerClass的静态成员变量instance在方法区中只会有一个实例。
在Java规范中,当以下这些情况首次发生时,A类将会立刻被初始化:
- A类型实例被创建;
- A类中声明的静态方法被调用;
- A类中的静态成员变量被赋值;
- A类中的静态成员被使用(非常量);
饿汉单例模式
“饿汉”意指在类加载的时候就初始化:
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
这种模式在类加载的时候就完成了初始化,所以并不存在线程安全性问题;但由于不是懒加载,饿汉模式不管需不需要用到实例都要去创建实例,如果创建了不使用,则会造成内存浪费。
序列化破坏单例模式
前面的单例例子在实现序列化接口后都能被序列化的方式破坏,比如HungrySingleton,让其实现序列化接口:
public class HungrySingleton implements Serializable {
private static final long serialVersionUID = -8073288969651806838L;
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
然后创建Application测试一下如何破坏:
public class Application {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 演示序列化破坏单例
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));
outputStream.writeObject(instance);
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));
HungrySingleton newInstance = (HungrySingleton) inputStream.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出如下所示:
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@6d03e736
false
可以看到,虽然是单例模式,但却成功创建出了两个不一样的实例,单例遭到了破坏。
要让反序列化后的对象和序列化前的对象是同一个对象的话,可以在HungrySingleton里加上readResolve方法:
public class HungrySingleton implements Serializable {
private static final long serialVersionUID = -8073288969651806838L;
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
// 新增
private Object readResolve() {
return instance;
}
}
再次运行Application的main方法后:
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@7f31245a
true
可以看到,这种方式最终反序列化出来的对象和序列化对象是同一个对象。但这种方式反序列化过程内部还是会重新创建HungrySingleton实例,只不过因为HungrySingleton类定义了readResolve方法(方法内部返回instance引用),反序列化过程会判断目标类是否定义了readResolve该方法,是的话则通过反射调用该方法。
反射破坏单例模式
除了序列化能破坏单例外,反射也可以,举个反射破坏HungrySingleton的例子:
public class Application {
public static void main(String[] args) throws Exception {
HungrySingleton instance = HungrySingleton.getInstance();
// 反射创建实例
Class<HungrySingleton> c = HungrySingleton.class;
// 获取构造器
Constructor<HungrySingleton> constructor = c.getDeclaredConstructor();
// 打开构造器权限
constructor.setAccessible(true);
HungrySingleton newInstance = constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出如下所示:
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@1b6d3586
cc.mrbird.design.pattern.creational.singleton.HungrySingleton@4554617c
false
可以看到,我们通过反射破坏了私有构造器权限,成功创建了新的实例。
对于这种情况,饿汉模式下的例子可以在构造器中添加判断逻辑来防御(懒汉模式的就没有办法了),比如修改HungrySingleton的代码如下所示:
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
if (instance != null) {
throw new RuntimeException("forbidden");
}
}
public static HungrySingleton getInstance() {
return instance;
}
}
再次运行Application的main方法:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at cc.mrbird.design.pattern.creational.singleton.Application.main(Application.java:33)
Caused by: java.lang.RuntimeException: forbidden
at cc.mrbird.design.pattern.creational.singleton.HungrySingleton.<init>(HungrySingleton.java:16)
... 5 more
枚举单例模式
枚举单例模式是推荐的单例模式,它不仅可以防御序列化攻击,也可以防御反射攻击。举个枚举单例模式的代码:
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
验证下是否是单例的:
public class Application {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.getInstance();
instance.setData(new Object());
EnumSingleton newInstance = EnumSingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance.getData());
System.out.println(newInstance.getData());
}
}
输出如下所示:
INSTANCE
INSTANCE
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586
测试下序列化攻击:
public class Application {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.getInstance();
instance.setData(new Object());
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));
outputStream.writeObject(instance);
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));
EnumSingleton newInstance = (EnumSingleton) inputStream.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
System.out.println(instance.getData());
System.out.println(newInstance.getData());
System.out.println(instance.getData() == newInstance.getData());
}
}
输出如下所示:
INSTANCE
INSTANCE
true
java.lang.Object@568db2f2
java.lang.Object@568db2f2
true
可以看到序列化和反序列化后的对象是同一个。
原理:跟踪ObjectInputStream#readObject源码,其中当反编译对象为枚举类型时,将调用readEnum方法:

name为枚举类里的枚举常量,对于线程来说它是唯一的,存在方法区,所以通过Enum.valueOf((Class)cl, name)方法得到的枚举对象都是同一个。
再测试一下反射攻击:
public class Application {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.getInstance();
Class<EnumSingleton> c = EnumSingleton.class;
// 枚举类只包含一个(String,int)类型构造器
Constructor<EnumSingleton> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingleton newInstance = constructor.newInstance("hello", 1);
System.out.println(instance == newInstance);
}
}
运行输出如下:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at cc.mrbird.design.pattern.creational.singleton.Application.main(Application.java:71)
可以看到抛异常了,查看Constructor类的417行代码可以发现原因:
Java禁止通过反射创建枚举对象。
正是因为枚举类型拥有这些天然的优势,所以用它创建单例是不错的选择,这也是Effective Java推荐的方式。
原型模式
原型实例指定创建对象的种类,通过拷贝这些原型创建新的对象。
适用于:
- 类初始化消耗较多资源;
- 循环体中生产大量对象的时候。
优点:
- 原型模式性能比直接new一个对象性能好;
- 简化创建对象过程。
缺点:
- 对象必须重写Object克隆方法;
- 复杂对象的克隆方法写起来较麻烦(深克隆、浅克隆)
举例:新建一个学生类Student,实现克隆接口,并重写Object的克隆方法(因为都是简单属性,所以浅克隆即可):
public class Student implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
在Application中测试一波:
public class Application {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
ArrayList<Student> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Student s = (Student) student.clone();
s.setName("学生" + i);
s.setAge(20 + i);
list.add(s);
}
System.out.println(list);
}
}
输出如下所示:
[Student{name='学生0', age=20}, Student{name='学生1', age=21}, Student{name='学生2', age=22}]
这种方式会比直接在循环中创建Student性能好。
当对象包含引用类型属性时,需要使用深克隆,比如Student包含Date属性时:
public class Student implements Cloneable {
private String name;
private int age;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
// 引用类型深克隆
Date birthday = (Date) student.getBirthday().clone();
student.setBirthday(birthday);
return student;
}
}
值得注意的是,克隆会破坏实现了Cloneable接口的单例对象。

浙公网安备 33010602011771号