Java设计模式_创建型模式

概述

设计模式总结,创建型模式篇.

创建型模式

创建型模式,主要作用是创建对象.最简单的方式就是new一个对象.通过构造器或者set方法注入属性.也可以通过设计模式提供更好的创建对象的方式.

简单工厂模式

只生产同一类型的工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PhoneFactory {
public static Phone makePhone(String brand) {
if (brand.equals("huawei")) {
Phone huawei = new HUAWEIphone();
huawei.addType("mate30");
return huawei;
} else if (brand.equals("apple")) {
Phone iphone = new ApplePhone();
iphone.addType("11pro");
return iphone;
} else {
return null;
}
}
}

简单工厂模式.一个工厂模式(xxxFactory),通过静态方法传入特定的参数,根据不同的参数,多个子类(或实现同一接口)的不同的对象(huawei&apple)并返回给调用者.强调一个工厂类只生产同一个类型,PhoneFactory就只是负责生产各个类型的phone.

工厂模式

当需要两个以上的工厂时.

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
public interface PhoneFactory {
Phone makePhone(String type);
}

public class HUAWEIPhoneFactory implements PhoneFactory {
@Override
public Phone makePhone(String type) {
if (type.equals("2019")) {
return new Mate30();
} else if(type.equals("2018")) {
return new Mate20();
} else {
return null;
}
}
}

public class ApplePhoneFactory implements PhoneFactory {
@Override
public Phone makePhone(String type) {
if (type.equals("2019")) {
return new Iphone11();
} else if(type.equals("2018")) {
return new IphoneXR();
} else {
return null;
}
}
}

其中Mate20,Mate30,Iphone11,IphoneXR都继承自Phone.

调用代码

1
2
3
4
5
6
7
8
9
10
11
public class APP {
public static void main(String[] args) {
//创建一个具体的工厂对象
PhoneFactory factoryA = new HUAWEIPhoneFactory();
//根据不同的工厂造出不一样的对象
Phone phoneA = factoryA.makePhone("2019");

PhoneFactory factoryB = new ApplePhoneFactory();
Phone phoneB = factoryB.makePhone("2019");
}
}

虽然factory.makePhone(“2019”);都是传入同样的参数(“2019”),但是创建的对象却是不一样的.

创建工厂对象的类型,决定了生产什么品牌的phone.

抽象工厂模式

涉及到统一类型(品牌)的对象,需要引入抽象工厂模式了.

前面的工厂模式,生产的对象会出现一个问题.

1
2
3
4
5
6
7
8
9
10
//生产华为的数据线type-c
DatalineFactory datalineFactory = new TypeCFactory();
Dataline dataline = TypeCFactory.makeDataline();

//生产苹果的手机
PhoneFactory applePhoneFactory = new ApplePhoneFactory();
ApplePhone applePhone = applePhoneFactory.makePhone();

//包装手机
PhoneBox box = new box(applePhone, dataline);

apple家生产的手机和华为家生产的数据线并不能通用.容易出错.

此时需要抽象工厂模式.

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
public interface PhoneFactory {
Dataline makeDataline();
Phone makePhone();
}

public class HUAWEIPhoneFactory implements PhoneFactory {
@Override
public Phone makePhone() {
return new HuaweiPhone();
}
@Override
Dataline makeDataline() {
return new TypeC();
}
}

public class ApplePhoneFactory implements PhoneFactory {
@Override
public Phone makePhone() {
return new IPhone();
}
@Override
Dataline makeDataline() {
return new Lightning();
}
}

调用方式

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//根据品牌创建工厂对象
PhoneFactory pf = new ApplePhoneFactory();
//从这个品牌创建phone对象
Phone iPhone = cf.makePhone();
//从这个品牌工厂创建数据线对象
Dataline lightning = cf.makeDataline();

//将同一个品牌工厂生产的对象包装在一起
PhoneBox box = new box(iPhone, lightning);
}

此时不再需要单独选择Factory生产手机和数据线了.只需要选择该品牌下的Factory,会针对性的生产互相兼容的产品.


单例模式

饿汉式
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 instance;
}
}

优点:

  • 线程安全.
  • 在类加载的同时,已经创建好一个静态对象,调用时无需再次创建.

缺点:

  • 资源效率不高.无论是否需要用到getInstance()方法,但是在执行该类的其他静态方法或者加载了该类(class.forName)时,依然会创建对象.
懒汉式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {
//私有化该类的构造器,保证其他类无法创建该类的对象.
private Singleton() {}
//不需要先创建对象,volatile关键字保证变量在多线程下的可见性
private static volatile Singleton instance = null;

public static Singleton getInstance() {
//第一次检查
if (instance == null) {
//加锁
synchronized (Singleton.class) {
//必须判断,否则会出现并发问题
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

双重检查,两次instance是否为null.

使用volatile修饰的变量,会保证线程可见性(当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值),但是并不会保证原子性(即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行)

使用volatile关键字修饰共享变量还可以禁止重排序(编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段).

比如a=1;b=2;c=a+b.变量a和b可能会发生重排序(b=2先执行),因为a和b的执行顺序并不会影响到最终c的结果(c不会重排序).这是在单线程的前提下.

如果是多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestVolatile {
int a = 1;
boolean status = false;

//修改状态
public void changeStatus() {
a = 2;
status = true;
}

public void sum() {
//当状态为true时,执行.
if(status) {
int b = a + 1;
}
}
}

a = 2;和status = true;可能会发生重排序.status = true;先被执行,从而影响最终的结果.

嵌套类
1
2
3
4
5
6
7
8
9
10
11
public class Singleton3 {

private Singleton3() {}
//嵌套类可以访问外部类的静态属性和静态方法
private static class Holder {
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.instance;
}
}
枚举类

用枚举实现单例,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的.


建造者模式

先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,创建需要的对象.

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
class User {
//user属性
private String name;
private String password;
private String nickName;
private int age;

//私有化构造器,防止外界创建对象.
private User(String name, String password, String nickName, int age) {
this.name = name;
this.password = password;
this.nickName = nickName;
this.age = age;
}
// 静态方法,用于生成一个 Builder.可以直接使用User.builder()的方式调用.
public static UserBuilder builder() {
return new UserBuilder();
}

public static class UserBuilder {
//和User统一属性
private String name;
private String password;
private String nickName;
private int age;

//Builder 的构造方法中强制让调用者提供必填字段
private UserBuilder() {
}

//链式调用设置各个属性值,返回 this,即 UserBuilder
public UserBuilder name(String name) {
this.name = name;
return this;
}

public UserBuilder password(String password) {
this.password = password;
return this;
}

public UserBuilder nickName(String nickName) {
this.nickName = nickName;
return this;
}

public UserBuilder age(int age) {
this.age = age;
return this;
}

//build() 方法负责将 UserBuilder 中设置好的属性 注入 到 User 中。
//在注入属性之前检验
public User build() {
if (name == null || password == null) {
throw new RuntimeException("用户名和密码必填");
}
if (age <= 0 || age >= 150) {
throw new RuntimeException("年龄不合法");
}
// 还可以做赋予"默认值"的功能
if (nickName == null) {
nickName = name;
}
return new User(name, password, nickName, age);
}
}
}

当属性很多,而且有些必填,有些选填的时候.建造者模式会使代码清晰很多.

在 build() 方法中校验各个参数比在 User 的构造方法中校验,可以让代码优雅一些.

lombok 插件,使用@Builder注解

1
2
3
4
5
6
7
@Builder
class User {
private String name;
private String password;
private String nickName;
private int age;
}

提供了建造者模式的注解.


原型模式

一个原型实例,基于这个原型实例产生新的实例,也就是clone.

Object 类中有一个 clone() 方法,它用于生成一个新的对象.Java要求该类必须先实现 Cloneable 接口.如果没有实现该接口,在 clone() 的时候,就会抛出 CloneNotSupportedException 异常.

java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用都会指向同一个内存地址.

通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化.


总结

简单工厂模式,通过一个工厂,创建同一类型的对象.

工厂模式,需要创建对应的工厂,如果根据该工厂对象创建对应的对象.

抽象工厂模式,抽象出来一个类型,在该类型上不必关心兼容,解决存在兼容性问题.

单例模式,创建单个对象,为安全的同时,节省资源.

建造者模式,当一个类属性很多,有些必填,有些选填的时候,可以使用建造者模式.让代码更加优雅.

原型模式,了解 Object 类中的 clone() 方法相关的知识即可,用的比较少.