# 创建者模式

创建型模式主要关注点是 “增氧创建对象”, 它的主要特点是 “将对象的创建与使用分离”

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工厂模式
  • 原型模式
  • 建造者模式

# 单例设计模式

单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象

# 单例模式的结构

单例模式主要有以下角色 :

  • 单例类。只能创建一个实例的类
  • 访问类。使用但单例类

# 单例模式的实现

单例设计模式分为两种 :

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

  1. 饿汉式 - 方式 1 (静态变量方式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class SingleTon {
    private SingleTon() {
    }

    private static SingleTon instance = new SingleTon();

    public static SingleTon getInstance() {
    return SingleTon.instance;
    }
    }

    该方法在成员位置声明 SingleTon 类型的静态变量,并创建 SingleTon 类的对象 instance. instance 对象是随着类的加载而创建的。如果对象足够大而一直没有使用就会造成内存的浪费

  2. 饿汉式 - 方式 2 (静态代码块方式)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class SingleTon1 {
    private static SingleTon1 instance;

    private SingleTon1() {
    }

    static {
    instance = new SingleTon1();
    }

    public static SingleTon1 getInstance() {
    return instance;
    }
    }

    该方式在成员位置声明 SingleTon 类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。所以也存在内存浪费的问题

  3. 懒汉式 - 方式 1 (线程不安全)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class LazyChineseStyle {
    private static LazyChineseStyle instance;
    private LazyChineseStyle(){}

    public static LazyChineseStyle getInstance() {
    if (instance == null) {
    instance = new LazyChineseStyle();
    }
    return instance;
    }
    }

    上面的代码在在成员位置声明 LazyChineseStyle 类型的静态变量,并没有进行对象的赋值操作,当调用 getInstance () 方法获取 LazeChineseStyle 类的对象时才创建对象,这样就实现了懒加载的效果,但是,如果是多线程环境,会出现线程安全问题

  4. 懒汉式 - 方式 2 (线程安全)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class LazyChineseStyle {
    private static LazyChineseStyle instance;

    private LazyChineseStyle() {
    }

    public static synchronized LazyChineseStyle getInstance() {
    if (instance == null) {
    instance = new LazyChineseStyle();
    }
    return instance;
    }
    }

    对于 getInstance 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没有必要让每个线程必须持有锁才能调用该方法,需要调整加锁的时机

  5. 懒汉式 - 方式 3 (双检锁)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class LazyChineseStyle {
    private static LazyChineseStyle instance;

    private LazyChineseStyle() {
    }

    public static LazyChineseStyle getInstance() {
    if (instance == null) {
    synchronized (LazyChineseStyle.class) {
    if (instance == null) {
    instance = new LazyChineseStyle();
    }
    }
    }
    return instance;
    }
    }

    双检锁概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 null 检查,这样就可以保证线程安全了

    这样,实例化代码只用执行一次,后面再次访问时,判断 if 语句直接 return 实例化对象,也避免了反复进行 synchronized 同步

  6. 懒汉式 - 方式 4 (静态内部类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class LazyChineseStyle {
    private static LazyChineseStyle instance;

    private LazyChineseStyle() {
    }

    private static class LazyChineseStyleInstance {
    private static final LazyChineseStyle INSTANCE = new LazyChineseStyle();
    }

    public static LazyChineseStyle getInstance() {
    return LazyChineseStyleInstance.INSTANCE;
    }
    }
    • 这种方式采用了类装载的机制来保证初始化实例时只有一个线程

    • 静态内部类方式在 LazyChineseStyle 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 LazyChineseStyleInstance 类,从而完成 LazyChineseStyle 的实例化

    • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的

    • 优点:避免了线程不安全,利用静态内部类特点是先延迟加载,效率高

    • 缺点:只适合使用无参构造的单例类,无法传递参数

  7. 枚举

    1
    2
    3
    public enum Singleton2 {
    INSTANCE;
    }

    这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

# 简单工厂模式

  1. 简单工厂模式是由一个工厂对象决定创建出哪一种产品的实例。简单工厂模式是工厂模式家族最简单实用的模式
  2. 简单工厂模式:定义了一个创建对象的类,与偶这个类来封装实例化对象的行为
  3. 在软件开发中,当会用到大量的创建某类或者某批对象时,就会使用到工厂模式

使用简单工厂模式,创建不同的简单工厂类,比如 BJPizzaSimpleFactory, LDPizzaSimpleFactory 等等.

image-20221121141341769

上图就是将 Pizza 的制作过程抽象为一个 SimpleFactory 类

  • 优点:封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中进行修改,降低了客户代码修改的可能性,更加容易扩展
  • 缺点:增加新产品还是需要修改工厂类的代码,违背开闭原则

# 工厂方法模式

定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

工厂方法模式的主要角色 :

  • 抽象工厂 (Abstract Factory) : 提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
  • 具体工厂 (ConcreateFactory) : 主要是实现抽象工厂中的抽象方法,完成具体产品的创建
  • 抽象产品 (Product) : 定义了产品的规范,描述了产品的主要特征和功能
  • 具体产品 (ConcreateProduct) : 实现了抽象产品角色所定义的接口,由具体工厂来实现,它同具体工厂之间意义对应

image-20221121143134741

优点 :

  • 用户只需要直到具体工厂的名称就可得到所要的产品,无需直到产品的具体创建过程

  • 在系统增加新的产品只需要添加具体产品类和对应的具体工厂类,无需对原工厂进行任何修改,满足开闭原则

缺点 :

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度

# 抽象工厂模式

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无需指定所要产品的具体类就能得到同组的不同等级的产品的模式结构

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级产品,而抽象工厂模式可生产多个等级的产品

抽象工厂模式的主要角色如下 :

  • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品
  • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体的产品创建
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系

image-20221121152702380

优点 :

  • 当一个产品族的多个对象被设计成一起工作时,它能保证客户端始终只使用同一产品族中的对象

缺点 :

  • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改

# 原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象

原型模式包含如下角色 :

  • 抽象原型类:规定了具体原型对象必须实现的 clone () 方法
  • 具体原型类:实现抽象类的 clone () 方法,它是可被复制的对象
  • 访问类:使用具体原型类中的 clone () 方法来复制新的对象

image-20221121160444954

# 实现

原型模式的克隆分为浅克隆和深克隆

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址

深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

Java 中的 Object 类中提供了 clone () 方法来实现浅克隆. Cloneable 接口是上面的类图中的抽象原型类,而实现了 Cloneable 接口的子实现类就是具体的原型类

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
public class Realizetype implements Cloneable {
private Hook hook;

public void setHook(Hook hook) {
this.hook = hook;
}

public Realizetype() {
}

public Hook getHook() {
return hook;
}

public Realizetype(String name) {
this.hook = new Hook();
hook.setName(name);
}

public Realizetype copy() throws CloneNotSupportedException {
return (Realizetype) this.clone();
}

//实现深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
Hook hook1 = new Hook();
hook1.setName(this.hook.getName());
Realizetype realizetype = new Realizetype();
realizetype.setHook(hook1);
return realizetype;
}

@Override
public String toString() {
return this.hook.getName();
}
}


public class Hook {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

# 建造者模式

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示

分离了部件的构造 (由 Builder 来负责) 和装配 (由 Director 负责). 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况

由于实现了构建和装配的解耦。不同的构造器,相同的装配,也可以做出不同的对象;相同的构建起,不同的装配顺序也能做出不同的对象。也就是实现了构建算法,装配算法的结构,实现了更好的复用

建造者模式可以将不减和其组装过程分开,一步步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而不许直到内部的具体构造细节

建造者模式包含如下角色 :

  • 抽象建造者类 (Builder) : 这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建
  • 具体建造者类 (ConcreteBuilder) : 实现 Builder 接口,完成复杂产品的各个不减的具体创建方法。在构造过程完成后,提供产品的实例
  • 产品类 (Product) : 要创建的复杂对象
  • 指挥者类 (director) : 调用具体建造者来创建复杂对象的各个部分,在指导者不涉及具体产品的信息,只负责保证对象各部分完整创建或按照某种顺序创建

image-20221121171324011

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class Bike {
private String frame;
private String seat;

public String getFrame() {
return frame;
}

public void setFrame(String frame) {
this.frame = frame;
}

public String getSeat() {
return seat;
}

public void setSeat(String seat) {
this.seat = seat;
}

@Override
public String toString() {
return "Bike{" +
"frame='" + frame + '\'' +
", seat='" + seat + '\'' +
'}';
}
}



public abstract class Builder {
protected Bike bike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}



public class Director {
private Builder builder;

public Director(Builder builder) {
this.builder = builder;
}

public Bike construct() {
builder.buildFrame();
builder.buildSeat();
return builder.createBike();
}
}



public class MoBileBuilder extends Builder{
@Override
public void buildFrame() {
bike.setFrame("碳纤维");
}

@Override
public void buildSeat() {
bike.setSeat("真皮");
}

@Override
public Bike createBike() {
return bike;
}
}



public class OfoBuilder extends Builder {
@Override
public void buildFrame() {
bike.setFrame("铝合金");
}

@Override
public void buildSeat() {
bike.setSeat("橡胶");
}

@Override
public Bike createBike() {
return bike;
}
}

image-20221121172315014

** 注意 : **

上面示例是 Builder 模式的常规用法,指挥者类 Director 在建造者模式中有着很重要的作用,它用于直到具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

优点 :

  • 建造者模式的封装性很好。使用建造者模式可以有效地封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性
  • 在建造者模式中,客户端不必知道产品内部组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更加方便使用程序来控制创建过程
  • 建造者模式很容易进行扩展,如果由新的需求,则通过实现一个新的建造者类就可以完成。基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险,符合开闭原则

缺点 :

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异很大,则不适合使用建造者模式,因此其使用范围收到一定的限制

使用场景 :

​ 建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用

  • 创建的对象比较复杂,由多个部件构成,各部件面领着复杂的变化,但构建的建造顺序是稳定的
  • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的
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
53
54
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;
private Phone (Builder builder) {
this.cpu = builder.cpu;
this.mainboard = builder.mainboard;
this.memory = builder.memory;
this.screen = builder.screen;
}

@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}

public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;

public Builder cpu(String cpu) {
this.cpu = cpu;
return this;
}

public Builder screen(String screen) {
this.screen = screen;
return this;
}

public Builder memory(String memory) {
this.memory = memory;
return this;
}

public Builder mainboard(String mainboard) {
this.mainboard = mainboard;
return this;
}

public Phone build() {
return new Phone(this);
}
}
}

以上是建造者模式扩展,使用链式编程来增加可读性 (LomBok 的 @Builder 注解也可以实现)

# 创建者模式对比

  • 工厂方法模式 vs 建造者模式

    工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象

  • 抽象工厂模式 vs 建造者模式

    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可

    建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品