5种创建型模式:单例、工厂、抽象工厂、建造者、原型。

单例模式 Singleton

类图

使用场景

  1. 要求生成唯一序列号的环境。
  2. 整个项目需要一个共享访问点或共享数据。例如web页面上的计数器,使用单例保持计数器的值。
  3. 创建对象需要消耗过多资源。例如访问IO和数据库等。
  4. 需要定义大量静态常量和静态方法(如工具类)的环境(也可直接声明为 static 的方式)。

优点

  1. 内存中只有一个实例,减少了内存开销。避免对象频繁的创建、销毁。
  2. 减少系统性能开销。可在应用启动时之间产生单例对象去读取配置,产生其他依赖对象等。
  3. 避免对资源的多重占用。例如写文件只由单例去完成。
  4. 设置全局访问点,优化和共享资源访问。例如由单例类负责所有数据表的映射处理。

缺点

  1. 一般没有接口,扩展困难。提供单一实例、接口或抽象类。
  2. 对测试不利。单例没有完成没法测试,业不能 mock 一个虚拟对象。
  3. 与单一职责原则由冲突。一个雷实现一个逻辑,不关心是否单例。是不是单例取决于环境,单例模式把要单例 和 业务逻辑 融合在一个类中。

注意事项

  1. 线程同步问题

    高并发时,可能会出现该问题。解决办法:饿汉、懒汉、二次检查等。

  2. 对象的复制情况

    如果实现了 Cloneable 接口,即使私有构造函数对象仍然可以被复制。因为克隆不需要调用类的构造函数。对于单例类最好不要实现 Cloneable 接口。

扩展

  1. 有上限的单例模式**

    决定内存中有多少个实例,修正单例可能存在的性能问题,提供系统的响应速度,例如读取文件在系统启动时完成初始化工作,在内存中启动固定数量的 reader 实例,然后在需要读取文件时就可以快速响应。

代码演示

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
/**
* 懒汉模式
* 线程不安全,延迟初始化,严格意义上不是不是单例模式
*/
public class Singleton1 {
private static Singleton1 instance;

private Singleton1() {}

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

/**
* 饿汉模式
* 线程安全,比较常用,但容易产生垃圾,因为一开始就初始化
*/
class Singleton2 {
private static Singleton2 instance = new Singleton2();

private Singleton2() {}

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

/**
* 双重锁模式
* 线程安全,延迟初始化。采用双锁机制,安全且在多线程情况下能保持高性能
*/
class Singleton3 {
// 避免对象创建时可能在JVM中重排序
private volatile static Singleton3 instance;

private Singleton3() {}

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

/**
* 静态内部类单例模式
* 只有第一次调用 getInstance() 时,虚拟机才加载内部类并初始化 instance
* 只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性
* 目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
*/
class Singleton4 {
private Singleton4() {}

public static Singleton4 getInstance() {
return InnerClass.INSTANCE;
}

private static class InnerClass {
private static final Singleton4 INSTANCE = new Singleton4();
}
}

/**
* 枚举单例模式
* 默认枚举实例的创建时线程安全的,并且在任何情况下都是单例
* 实际上:枚举类隐藏了私有的构造器;枚举类的域是相应类型的一个实例对象
*/
enum Singleton5 {
INSTANCE;

public static Singleton5 getInstance() {
return Singleton5.INSTANCE;
}
}

工厂方法模式 Factory

类图

使用场景

  1. 需要灵活可扩展的框架时。比如需要设计一个链接邮件服务器的框架,有3种网络协议可选:POP3、IMAP、HTTP,作为产品类,定义接口IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(连接方式),再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。
  2. 用在异构项目中。例如通过webservice与非java项目交互,虽然webservice好撑可以做到异构系统的同构化,但实际会遇到类型问题、WSDL文件的支持问题等。从WSDL中产生的对象都认为是一个产品,然后由一个具体的工厂类进行管理,减少与外围系统的耦合。
  3. 使用在测试驱动开发的框架下。例如测试类A,把与A有关联的B虚拟出来,避免A与B的耦合。该功能由于 JMock 和 EasyMock 的诞生使用场景已经弱化了。

优点

  1. 封装良好,代码结构清晰。只需知道产品类名或约束字符串,屏蔽创建过程,降低模块间的耦合。
  2. 优秀的扩展性。只需增加扩展类,无需修改工厂类。
  3. 屏蔽产品类。接口不变系统中的上层模块就不要发生变化。例如 JDBC 连接数据库,切换数据库需要改动的只是驱动名称。
  4. 解耦框架。高层模块只需要指导产品的抽象类,符合迪米特法则(只关心需要关心的)、依赖倒置原则(只依赖产品类的抽象)、里氏替换原则(使用产品子类替换父类)。

扩展

  1. 缩小为简单工厂模式

  2. 升级为多个工厂类

  3. 代替单例模式

  4. 延迟初始化

    通过map缓存需要重用的对象

代码演示

  1. 简单工厂模式

    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 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 {
    @Override
    public String getName() {
    return "奔驰";
    }
    }

    class Bmw implements Car {
    @Override
    public String getName() {
    return "宝马";
    }
    }
  2. 工厂模式

    不能存在一个工厂生产所有产品的情况,所以需要多个工厂进行不同产品的生产。

    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 {
    @Override
    public Car getCar() {
    return new Benz();
    }
    }

    class BmwFactory implements Factory {
    @Override
    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());
    }
    }
  3. 替代单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public 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 {}
  4. 延迟初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public 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

类图

使用场景

  1. 一个对象族(或是一组没有任何关系的对象)都有相同的约束,就可以使用抽象工厂模式。比如文本编辑器和图片处理器都是软件,但是 linux 和 windows 下文本编辑器虽然界面相同,但是代码不同,也就有了共同的约束条件——操作系统。

优点

  1. 封装性好。高层模块不关心每个产品的实现类,值关心接口。对象的创建由工厂类负责。高层模块只需要知道工厂类有哪些就行。
  2. 产品族内的约束为非公开状态。例如下面代码中约定每生产1个发动机,需要设计出3种外形与之匹配。

缺点

  1. 产品族扩展非常困难。如果新增一个产品,需要修改抽象工厂类、几个实现类,严重违反开闭原则。

注意事项

  1. 与工厂模式的区别

    工厂模式用来创建同一个产品的不同类型,如汽车里的奔驰、宝马,抽象工厂模式用来创建不同类的产品,汽车工厂设计汽车造型,生产发动机等。一般来说产品种类单一适合用工厂模式,如果有多个种类各种类型,更适合用抽象工厂模式。

  2. 产品族扩展困难,产品等级扩展容易

    也就是横向扩展容易,纵向扩展困难。例如下面代码新增车型奥迪,只需要新增奥迪工厂类即可。从这一点上,抽象工厂符合开闭原则。

代码演示

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
// 抽象工厂
public abstract class CarFactory {
abstract DesignFactory designAppearance();
abstract EngineFactory produceEngine();
}

// 产品类型
abstract class DesignFactory {
abstract void design();
}

abstract class EngineFactory {
abstract void produce();
}


class BmwDesignFactory extends DesignFactory {
@Override
public void design() {
System.out.println("设计宝马造型");
}
}

class BenzDesignFactory extends DesignFactory {
@Override
public void design() {
System.out.println("设计奔驰造型");
}
}

class BmwEngineFactory extends EngineFactory {
@Override
public void produce() {
System.out.println("生产宝马发动机");
}
}

class BenzEngineFactory extends EngineFactory {
@Override
public void produce() {
System.out.println("生产奔驰发动机");
}
}

// 产品工厂
class BmwFactory extends CarFactory {
@Override
public DesignFactory designAppearance() {
return new BmwDesignFactory();
}

@Override
public EngineFactory produceEngine() {
return new BmwEngineFactory();
}
}

// 测试类
class Test {
public static void main(String[] args) {
BmwFactory bmwFactory = new BmwFactory();
bmwFactory.designAppearance().design();
bmwFactory.produceEngine().produce();
}
}

建造者模式 Builder

类图

使用场景

  1. 相同的方法,不同的执行顺序,产生不同的事件结果。
  2. 多个部件或零件都可以装配到一个对象中,但是产生的运行结果又不相同。
  3. 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能。
  4. 对象创建过程中会使用到系统中的一些其他对象,这些对象在产品创建过程中不易得到时,可采用建造者模式封装该对象的创建过程。这种场景只能是一个补偿方法,因为一个对象不容易获得,在设计阶段竟然没发觉,而要通过创建者模式柔化创建过程,本身已经违反设计的最初目标。
  5. 当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性差,可用 builder 模式重构。如 lombok 的 @Builder 注解。

优点

  1. 封装性好。使客户端不必知道产品内部组成的细节,只关注产生的对象和要素。
  2. 建造者独立,更容易扩展。多个 builder 相互独立,对系统的扩展非常有利。
  3. 便于控制细节风险。具体建造者独立,因此可以对建造过程逐步细化,而不对其他模块产生任何影响。

注意事项

  1. 与工厂模式的区别

    建造者模式最主要的功能是方法的调用顺序安排,通俗讲就是零件的装配,顺序不同对象也不同。即关注的是零件类型和装配工艺(顺序)。

    工厂方法主要职责是创建零件,而不关心组装顺序。

扩展

  1. 与模板方法模式进行结合

代码演示

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
public abstract class Builder {
public abstract void setPart();
public abstract Product build();
}

@Data
class Product {
private String name;

public void doSomeThing() {
System.out.println("产品做某事");
}
}

class ConcreteProduct extends Builder {
private Product product = new Product();
@Override
public void setPart() {
product.setName("默认名字");
}

@Override
public Product build() {
return product;
}
}

class Director {
private Builder builder;

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

public Product getBuilder() {
builder.setPart();
return builder.build();
}
}

class Client {
public static void main(String[] args) {
Director director = new Director(new ConcreteProduct());
Product product = director.getBuilder();
product.doSomeThing();
}
}

原型模式 Prototype

使用场景

  1. 资源优化场景。类初始化消耗较多资源,包括数据、硬件等。
  2. 性能和安全要求的场景。避免通过 new 产生一个对象需要非常繁琐的数据准备或访问权限。
  3. 一个对象多个修改者的场景。拷贝多个对象供各个调用者使用。
  4. 原型模式一般与工厂方法模式一起使用,通过 clone 方法创建一个对象,然后由工厂方法提供给调用者。

优点

  1. 性能优良。内存二进制流的拷贝,比直接 new 一个对象性能好很多,特别是在循环体内产生大量对象。
  2. 逃避构造函数的约束。直接内存拷贝,不会执行构造函数,即使是 private 也可以克隆。双刃剑。

注意事项

  1. 构造函数不会被执行

  2. 深拷贝和浅拷贝

    使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二必须时一个可变得引用对象,而不是原始类型或不可变对象。

    深拷贝和浅拷贝建议不要混合使用,特别是涉及类的继承时,父类有多个引用的情况就非常复杂,建议深拷贝和浅拷贝分开实现。

  3. clone 与 final 有冲突。要使用 clone,类成员变量上就不能加 final 修饰。

代码演示

  1. 构造函数不执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Prototype implements Cloneable {
    public Prototype() {
    System.out.println("构造函数被执行。。");
    }
    @Override
    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();
    }
    }
  2. 深拷贝与浅拷贝

    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<>();
    @Override
    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<>();
    @Override
    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());
    }
    }
  3. 增加 final 的拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class FinalClone implements Cloneable {
    private final ArrayList<String> list = new ArrayList<>();

    @Override
    protected FinalClone clone() throws CloneNotSupportedException {
    FinalClone clone = (FinalClone) super.clone();
    // 下面这行代码报错
    // clone.list = (ArrayList<String>) this.list.clone();
    return clone;
    }
    }

 評論