定义
- 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象
- 不需要知道任何创建细节,不调用构造函数
适用场景
- 创建对象麻烦或困难时。1.对象种类繁多,无法整合到一个类时 2.要创建一个类,初始化时需要使用较多资源。
- 想解耦框架和生成实例时,生成的框架不依赖于具体的类。
优点
- 创建过程简单
- 原型模式性能比直接new一个对象性能高
缺点
- 必须配备克隆方法
- 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入代码BUG
- 必须清楚了解深拷贝与浅拷贝
原型模式中的角色
角色说明
-
Prototype(原型)
- Product角色负责定义用于复制现有实例来生成新实例的方法。在实例程序中,由
Cloneable
接口来扮演此角色。
- Product角色负责定义用于复制现有实例来生成新实例的方法。在实例程序中,由
-
ConcreteProrotype(具体原型)
- ConcretePrototype角色负责实现复制现有实例并生成新实例方法。在实例程序中,由
User
类扮演此角色。
- ConcretePrototype角色负责实现复制现有实例并生成新实例方法。在实例程序中,由
-
Client(使用者)
- Client角色负责使用复制实例的方法生成新的实例。在实例程序中,由单元测试类扮演此角色。
类图
代码实现
原型模式代码
写法一
1.用java实现原型模式比较简单,只需要将目标类实现Cloneable
接口即可
@Getter
@Setter
@AllArgsConstructor
public class User implements Cloneable {
private String name;
private Date birthday;
public User() {
System.out.println("User constructor被调用");
}
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("User clone被调用");
// 浅克隆写法
return super.clone();
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+super.toString();
}
}
写法二
- 抽象父类实现Cloneable接口
public abstract class AbstractUser implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("抽象父类clone方法被调用");
return super.clone();
}
}
- 目标类继承抽象父类接口
@Getter
@Setter
public class User extends AbstractUser {
private String name;
private Date birthday;
public User() {
System.out.println("User constructor被调用");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+super.toString();
}
}
单元测试(写法一)
测试代码
@Test
public void testClone() throws CloneNotSupportedException {
Date birthday = new Date(0L);
// 1. 创建用户对象
User user1 = new User();
user1.setName("小林");
user1.setBirthday(birthday);
// 2. 克隆用户对象
User user2 = (User) user1.clone();
// 3. user1,user2对比
System.out.println(user1);
System.out.println(user2);
System.out.println(user1 == user2);
System.out.println("<==============我是分割线============>");
// 4. 赋值新的birthday并打印
user1.getBirthday().setTime(666666666666L);
System.out.println(user1);
System.out.println(user2);
System.out.println(user1 == user2);
}
执行结果
根据user1和user2的hash值不同可以看出是不同的类,但user1的birthday修改后,user2的birthday也同样被修改,说明user1和user2指向了同一个birthday,这里就引发深拷贝与浅拷贝的问题。
深拷贝 VS 浅拷贝
使用clone()
,之后,由于birthday
是一个引用对象,由于是浅拷贝,user中的引用成员变量依然指向的是同一个。
什么是深拷贝,我们clone
user对象的同时,也将其内部引用的对象进行拷贝,使得每个引用对象无关联,都是单独的对象。
深拷贝代码改造
重新改造user中clone()
方法的实现
@Getter
@Setter
public class User implements Cloneable {
private String name;
private Date birthday;
public User() {
System.out.println("User constructor被调用");
}
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("User clone被调用");
// 浅拷贝写法
// return super.clone();
// 深拷贝写法
User user = (User) super.clone();
user.birthday=(Date) user.getBirthday().clone();
return user;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}'+super.toString();
}
}
执行结果
可以看到改造完成之后user1的birthday与user2的birthday指向不是同一个birthday了。
总结
使用原型模式时要十分注意深拷贝、浅拷贝的问题,即使了解了深拷贝和浅拷贝,在写代码的过程中一个疏忽就可能产生BUG,如果对深拷贝和浅拷贝不了解同学,需要慎用原型模式。