概念
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,
用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型模式定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象
模拟需求
现在有一辆车,他的名字叫做哈啰单车,它的价格是2元/1小时,请编写程序创建多辆哈啰单车
简单分析后涉及到以下几个类:
- 车辆类 Vehicle.java
- 测试类 Client.java
传统方式
先来看下最容易理解的方式:
1 2 3 4 5 6 7
| public class Vehicle {
private String name; private double price;
}
|
1 2 3 4 5 6 7 8 9 10
| public class Client { public static void main(String[] args) { Vehicle vehicle = new Vehicle("哈啰单车", 2.0); Vehicle vehicle1 = new Vehicle(vehicle.getName(),vehicle.getPrice()); Vehicle vehicle2 = new Vehicle(vehicle.getName(),vehicle.getPrice()); System.out.println(vehicle.hashCode()); System.out.println(vehicle1.hashCode()); System.out.println(vehicle2.hashCode()); } }
|
执行测试类代码可以看到创建了另外3个属性相同但引用完全不同的哈啰单车
执行结果如下:
1 2 3
| 1836019240 325040804 1173230247
|
原型模式
上面的对象复制方式是比较容易理解的,但是如果要复制很多对象时,每次都要get/set ,工作量必然很大
那有没有其他的复制方式吗?当然有了,设计模式中有一种原型模式的设计理念
原型模式的概念我们上文也有提到:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象
原型模式是一种创建型设计模式,创建方无需了解创建的细节,原型模式所涉及到的角色和类图如下
- 抽象原型类Prototype:抽象原型类,声明一个克隆自己的方法
- 具体的原型实现类ConcretePrototype:具体的原型类,实现克隆自己的方法
- 客户类Client:客户调用方,克隆对象

原型模式示例
对于模式场景中,要求复制多个不同的对象的需求,使用原型模式则有了新的解决方案如下
Java中Object类提供一个clone方法。该方法可以将一个Java对象复制一份。
如果某一个要使用clone方法,必须先实现Cloneable接口,Cloneable接口表示该类能够复制并且具有复制能力
涉及到的类和角色如下:
- 抽象原型类:Cloneable接口,声明了clone方法
- 具体原型类:Vehicle类,有自己的对象属性,并且实现了clone方法
- Client:调用测试
只需要将上面的代码加以修改即可:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Vehicle implements Cloneable {
private String name; private double price;
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
1 2 3 4 5 6 7 8 9 10 11
| public class Client { public static void main(String[] args) throws CloneNotSupportedException { Vehicle vehicle = new Vehicle("哈啰单车", 2.0); Vehicle vehicle1 = (Vehicle) vehicle.clone(); Vehicle vehicle2 = (Vehicle) vehicle.clone(); System.out.println(vehicle.hashCode()); System.out.println(vehicle1.hashCode()); System.out.println(vehicle2.hashCode()); } }
|
依旧执行测试方法,来看看通过clone方法是否复制出了不同的对象
执行结果:
1 2 3
| 1836019240 325040804 1173230247
|
通过上面的一个小场景,对原型模式进行了简单的演示。但是上面的原型模式在一些特殊情况下可能就会出现问题
模拟需求2
现在有一车辆类,他有名称、单价、所属公司三个属性;所属公司对象包含了公司名称属性,请编写程序创建多辆美团单车,单价为2元/小时,所属公司为美团点评
简单分析后涉及到以下几个类:
- 车辆类 Vehicle.java
- 公司类 Company.java
- 测试类 Client.java
在上文原型模式的代码中加上Company类的代码和Vehicle类的公司属性后如下
1 2 3 4 5
| public class Company { private String name;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Vehicle implements Cloneable {
private String name; private double price; private Company company;
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
Client代码如下
1 2 3 4 5 6 7 8 9 10 11
| public class Client { public static void main(String[] args) throws CloneNotSupportedException { Vehicle vehicle = new Vehicle("美团单车", 2.0, new Company("美团点评"));
Vehicle vehicle1 = (Vehicle) vehicle.clone(); Vehicle vehicle2 = (Vehicle) vehicle.clone(); System.out.println("vehicle.hashCode(): "+vehicle.hashCode()+" company.hashCode(): "+ vehicle.getCompany().hashCode()); System.out.println("vehicle1.hashCode(): "+vehicle1.hashCode()+" company.hashCode(): "+ vehicle1.getCompany().hashCode()); System.out.println("vehicle2.hashCode(): "+vehicle2.hashCode()+" company.hashCode(): "+ vehicle2.getCompany().hashCode()); } }
|
观察克隆对象后的输出结果,你就会发现问题所在
1 2 3
| vehicle.hashCode(): 1836019240 company.hashCode(): 325040804 vehicle1.hashCode(): 1173230247 company.hashCode(): 325040804 vehicle2.hashCode(): 856419764 company.hashCode(): 325040804
|
三个车辆对象的hashCode都不相同,说明有被成功克隆,但是其中的公司属性(对象类型)的hashCode并没有被同步克隆,内容中只有一份Company对象
相当于这次的克隆,内存中创建了三个不同车辆(Vehicle)对象,但是公司(Company)对象只有一个,被三个车辆对象所引用。
理论上,在创建了第一个车辆对象后,连续克隆两次后,内存中应该有3个车辆对象和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
| # 浅拷贝&深拷贝
关于浅拷贝的描述:
- 数据类型为基本类型的成员变量,在调用默认clone方法后,会进行浅拷贝,即将该属性的值复制一份给新的对象 - 数据类型为引用类型的成员变量,比如一个数组、一个对象,在调用默认的clone方法后,只会将成员变量的引用地址指向新的对象,而不会克隆新的成员变量对象
这种现象即为`浅拷贝`,上面的几个例子严格来说都属于浅拷贝,因为都没有去考虑成员变量为引用类型时的对象克隆
`深拷贝`自然是解决了浅拷贝的缺陷,对整个对象进行完全深度的对象复制,包括对象的引用类型和基本类型成员变量
# 深拷贝应用
针对模拟需求2,使用深拷贝的方式进行代码实现
实现思路:Company和Vehicle都实现Cloneable接口,重写Vehicle的clone方法
```java public class Company implements Cloneable { private String name; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } /**getter&setter&toString.......*/ }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Vehicle implements Cloneable {
private String name; private double price; private Company company;
@Override protected Object clone() throws CloneNotSupportedException { Vehicle vehicle = (Vehicle) super.clone(); vehicle.setCompany((Company) this.company.clone()); return vehicle; } }
|
Client代码无需变动,执行Client进行测试,结果如下:
1 2 3
| vehicle.hashCode(): 1836019240 company.hashCode(): 325040804 vehicle1.hashCode(): 1173230247 company.hashCode(): 856419764 vehicle2.hashCode(): 621009875 company.hashCode(): 1265094477
|
可以看到三个对象的对象成员属性明显都是不同的,说明做到了深拷贝
常见的原型模式的运用
Spring中配置bean的时候,scope属性可以配置一个prototype值,该值指定该bean的创建是使用原型模式
1 2
| //示例: <bean id="userDaoImpl" scope="prototype" class="com.larscheng.www.dao.impl.UserDaoImpl"/>
|
当通过getBean方法获取bean时,可以看到源码中对于scope属性进行了处理

结语
- 当我们要创建新的对象过于复杂时,可以考虑使用原型模式来进行创建
- 使用原型模式时,需要考虑到浅拷贝和深拷贝