原型模式

  1. 原型模式:通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象
  2. 使用这种方式创新的对象的话,就无需再通过new实例化来创建对象了
    • Object类的clone方法是一个Native方法,可以直接操作内存中的二进制流,所以相对new实例化来说,性能更佳

实现原型模式

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
class Prototype implements Cloneable {
@Override
public Prototype clone() {
Prototype prototype = null;
try {
prototype = (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototype;
}
}

class ConcretePrototype extends Prototype {
public void show() {
System.out.println("原型模式实现类");
}
}

public class Client {
public static void main(String[] args) {
ConcretePrototype cp = new ConcretePrototype();
for (int i = 0; i < 10; i++) {
ConcretePrototype prototype = (ConcretePrototype) cp.clone();
prototype.show();
}
}
}
  1. 实现Cloneable接口
    • Cloneable接口与Serializable接口的作用类似,告诉JVM可以安全地在实现了Cloneable接口的类上使用clone方法
    • 在JVM中,只有实现了Cloneable接口的类才可以被拷贝,否则会抛出CloneNotSupportedException
  2. 重写Object类的clone方法
    • Object类中有一个clone方法,作用是返回对象的一个拷贝
    • 在重写的clone方法中调用**super.clone()**,因为默认情况下,_类不具备复制对象的能力_
  3. 通过clone方法复制的对象是真正的对象复制,clone方法复制的对象是一个完全独立的对象
    • Object类的clone方法是一个Native方法,直接操作内存的二进制流,特别是复制大对象时,性能的差别特别明显
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
@Data
class Student implements Cloneable {
private String name;

@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}

public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setName("test1");

Student stu2 = stu1.clone();
stu2.setName("test2");

System.out.println(stu1.getName() + " : " + stu2.getName()); // test1 : test2
}
}

深拷贝 / 浅拷贝

  1. 在调用super.clone()方法后,首先会检查当前对象所属的类是否支持clone,即看该类是否实现了Cloneable接口
  2. 如果支持,则创建当前对象所属类的一个新对象,并对该对象进行初始化
    • 使得新对象的成员变量的值与当前对象的成员变量的值一模一样
    • 但对于其它对象的引用以及List等类型的成员属性,则只能复制这些对象的引用
    • 所以调用super.clone()的克隆对象方式,是一种浅拷贝
  3. 深拷贝其实就是基于浅拷贝来递归实现具体的每个对象

浅拷贝

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
// 浅拷贝
@Data
class Student implements Cloneable {
private String name;
private Teacher teacher;

@Override
public Student clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}

@Data
class Teacher implements Cloneable {
private String name;

@Override
public Teacher clone() {
Teacher teacher = null;
try {
teacher = (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return teacher;
}

}

public class Test {

public static void main(String args[]) {
Teacher teacher = new Teacher();
teacher.setName("刘老师");

Student stu1 = new Student();
stu1.setName("test1");
stu1.setTeacher(teacher);
Student stu2 = stu1.clone();
stu2.setName("test2");
stu2.getTeacher().setName("王老师");

System.out.println(stu1.getName() + " : " + stu1.getTeacher().getName()); // test1 : 王老师
System.out.println(stu2.getName() + " : " + stu2.getTeacher().getName()); // test2 : 王老师
}
}

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
class Student implements Cloneable {
private String name;
private Teacher teacher;

@Override
public Student clone() {
// 深拷贝
Student student = null;
try {
student = (Student) super.clone();
Teacher teacher = this.teacher.clone();
student.setTeacher(teacher);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}

适用场景

  1. 在一些重复创建对象的场景下,可以使用原型模式来提供对象的创建性能
  2. Spring中的@Service("prototype")

享元模式

  1. 享元模式是运用共享技术有效地最大限度地复用细粒度对象的一种模式
  2. 在享元模式中,以对象的信息状态划分,可以分为内部数据外部数据
    • 内部数据是对象可以共享出来的信息,这些信息不会随着系统的运行而改变
    • 外部数据则是在不同运行时被标记了不同的值
  3. 享元模式一般分为三个角色
    • Flyweight(抽象享元类):通常是一个接口或抽象类,向外界提供享元对象的内部数据外部数据
    • ConcreteFlyweight(具体享元类):实现内部数据共享的类
    • FlyweightFactory(享元工厂类):创建管理享元对象的工厂类

实现享元模式

Flyweight

1
2
3
4
5
6
7
interface Flyweight {
/* 对外状态对象 */
void operation(String name);

/* 对内对象 */
String getType();
}

ConcreteFlyweight

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@AllArgsConstructor
class ConcreteFlyweight implements Flyweight {
// 内部数据,不会随着系统的运行而改变
private String type;

@Override
public void operation(String name) {
System.out.printf("[类型 (内在状态)] - [%s] - [名字 (外在状态)] - [%s]\n", type, name);
}

@Override
public String getType() {
return type;
}
}

FlyweightFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FlyweightFactory {
// 享元池,用来存储享元对象
private static final Map<String, Flyweight> FLYWEIGHT_MAP = new HashMap<>();

public static Flyweight getFlyweight(String type) {
if (FLYWEIGHT_MAP.containsKey(type)) {
// 如果在享元池中存在对象,则直接获取
return FLYWEIGHT_MAP.get(type);
} else {
// 在享元池不存在,则新创建对象,并放入到享元池
ConcreteFlyweight flyweight = new ConcreteFlyweight(type);
FLYWEIGHT_MAP.put(type, flyweight);
return flyweight;
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
Flyweight fw0 = FlyweightFactory.getFlyweight("a");
Flyweight fw1 = FlyweightFactory.getFlyweight("b");
Flyweight fw2 = FlyweightFactory.getFlyweight("a");
Flyweight fw3 = FlyweightFactory.getFlyweight("b");
fw1.operation("abc"); // [类型 (内在状态)] - [b] - [名字 (外在状态)] - [abc]
System.out.printf("[结果 (对象对比)] - [%s]\n", fw0 == fw2); // [结果 (对象对比)] - [true]
System.out.printf("[结果 (内在状态)] - [%s]\n", fw1.getType()); // [结果 (内在状态)] - [b]
}
}

适用场景

  1. Java中的String,在一些字符串常量中,会共享常量池中字符串对象,从而减少重复创建相同值对象,减少空间占用
  2. 线程池Java本地缓存也是享元模式的一种实现
1
2
3
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true

参考资料

Java性能调优实战