设计模式●原型模式

原型模式(Prototype pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

原型模式是创建型模式的一种,其特点在于通过「复制」一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的「原型」,这个原型是可定制的。

在提到原型模式时,不得不提起两个概念:深拷贝、浅拷贝

  • 深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值 。
  • 浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。

简单地说:浅拷贝只是复制一个对象,传递引用,不能复制实例。而深拷贝则是对对象内部的引用均可复制,它是创建一个新的实例。

原型模式结构

简单原型模式

原型模式包含如下角色:

  • Client:调用类,客户端提出创建对象的请求
  • Prototype:原型类,可以是接口,一般情况下是由Java接口或抽象类来实现
  • ConcretePrototype:具体角色,该类是被复制的对象

原型模式

原型模式的核心就是Prototype类,该类必须实现Cloneable,然后重写clone方法,原因是在Java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。

登记形式的原型模式

登记形式的原型模式是较复杂的一种原型模式,它主要是多了一个原型管理器(PrototypeManager)角色,该角色创建具体原型类的对象,并记录每一个被创建的对象。

复杂原型模式

原型模式优点

  • 如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也可以提高效率。
  • 原型模式向客户隐藏了创建对象的复杂性。
  • 可以使用深克隆保持对象的状态。
  • 原型模式提供了简化的创建结构。

原型模式缺点

  • 在实现深克隆的时候可能需要比较复杂的代码。
  • 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行一个通彻的全盘考量,这对全新的来说并不是很困难,但是对已有的类进行改造的时候,必须要修改其源代码,这就违反了面向对象设计原则中的开闭原则

原型模式示例

在该示例中,我们以学生坐车为例

首先定义个学生类与汽车类

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
/**
* @author jiangliuhong
*/
public class Student implements Cloneable {

private String name;
private Car car;

public Student(String name, Car car) {
this.name = name;
this.car = car;
}

public String getName() {
return name;
}

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

public Car getCar() {
return car;
}

public void setCar(Car car) {
this.car = car;
}

public void print(){
System.out.println(this.name+"乘坐"+this.car.getName());
}

@Override
protected Student clone() {
try {
return (Student)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Car {
private String name;
public Car(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

在定义一个Client

1
2
3
4
5
6
7
8
9
10
public class SimpleMain {
public static void main(String[] args) {
Student student1 = new Student("小明",new Car("公交车"));
student1.print();
Student student2 = student1.clone();
student2.print();
System.out.println(student1 == student2);
System.out.println(student1.getCar() == student2.getCar());
}
}

运行该类输出结果如下:

1
2
3
4
小明乘坐公交车
小明乘坐公交车
false
true

从输出结果不难看出,该示例中,两个car变量是相等,说明,该操作只是实现的浅拷贝。下面再介绍一个深拷贝(即,深度克隆)的实现方式:

首先改造car对象,新建一个CarDeep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CarDeep extends Car implements Cloneable {

public CarDeep(String name) {
super(name);
}

@Override
protected CarDeep clone() {
try {
return (CarDeep)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

再新建一个StudentDeep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StudentDeep extends Student {
public StudentDeep(String name, CarDeep car) {
super(name, car);
}

@Override
protected Student clone() {
StudentDeep student = (StudentDeep) super.clone();
Car car = getCar();
if(car != null){
if(car instanceof CarDeep) {
student.setCar(((CarDeep) car).clone());
}
}
return student;
}
}

测试:

1
2
3
4
5
6
Student stuDeep1 = new StudentDeep("小明",new CarDeep("公交车"));
stuDeep1.print();
Student stuDeep2 = stuDeep1.clone();
stuDeep2.print();
System.out.println(stuDeep1 == stuDeep2);
System.out.println(stuDeep1.getCar() == stuDeep2.getCar());

输出结果:

1
2
3
4
小明乘坐公交车
小明乘坐公交车
false
false

原型模式总结

  • 克隆分为浅克隆和深克隆两种。
  • 原型模式向客户隐藏了创建对象的复杂性。客户只需要知道要创建对象的类型,然后通过请求就可以获得和该对象一模一样的新对象,无须知道具体的创建过程。
  • 我们虽然可以利用原型模式来获得一个新对象,但有时对象的复制可能会相当的复杂,比如深克隆。
  • 如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
  • 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
  • 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。