一、问题的提出
在应用程序中,有些对象比较复杂,其创建过程过于复杂,而且我们又需要频繁的利用该对象,如果这个时候我们按照常规思维new该对象,那么务必会带来非常多的麻烦,这个时候我们就希望可以利用一个已有的对象来不断对他进行复制就好了。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
二、原型模式
能够解决以上问题的模式称为原型模式。
原型模式:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
在原型模式中可以利用一个原型对象来指明要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。这就是原型模式的设计目的。
在原型模式中,所发动创建的对象通过请求原型对象来拷贝原型对象自己来实现创建过程,当然所发动创建的对象需要知道原型对象的类型。也就是说所发动创建的对象只需要知道原型对象的类型就可以获得更多的原型实例对象,至于这些原型对象时如何创建的根本不需要关心。
Tips:浅拷贝,深拷贝
先理解一下引用拷贝和对象拷贝
1、引用拷贝
Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);
输出结果:
blog.Teacher@355da254
blog.Teacher@355da254
结果分析:由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherteacher的只是引用而已,他们都指向了一个相同的对象Teacher(“Taylor”,26)。 这就叫做引用拷贝。
image.png
2、对象拷贝
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
输出结果:
blog.Teacher@355da254
blog.Teacher@4dc63996
结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。
image.png
深拷贝和浅拷贝都是对象拷贝
浅拷贝:对于基本类型的数据会进行拷贝,而对其他对象的引用仍然指向原来的对象。
深拷贝:基本类型和其他对象都会进行拷贝。
举个栗子:
1、浅拷贝
public class ShallowCopy {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher();
teacher.setName("Delacey");
teacher.setAge(29);
Student2 student1 = new Student2();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
Student2 student2 = (Student2) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------"); // 修改老师的信息
teacher.setName("Jam");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
class Teacher implements Cloneable {
private String name;
private int age;
public String get...
public void set...
}
class Student2 implements Cloneable {
private String name;
private int age;
private Teacher teacher;
public String get...
public void set...
@Override
public Object clone() throws CloneNotSupportedException {
Object object = super.clone(); return object;
}
}
输出结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam
结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。
image.png
2、深拷贝
public class DeepCopy {
public static void main(String[] args) throws Exception {
Teacher2 teacher = new Teacher2();
teacher.setName("Delacey");
teacher.setAge(29);
Student3 student1 = new Student3();
student1.setName("Dream");
student1.setAge(18);
student1.setTeacher(teacher);
Student3 student2 = (Student3) student1.clone();
System.out.println("拷贝后");
System.out.println(student2.getName());
System.out.println(student2.getAge());
System.out.println(student2.getTeacher().getName());
System.out.println(student2.getTeacher().getAge());
System.out.println("修改老师的信息后-------------"); // 修改老师的信息
teacher.setName("Jam");
System.out.println(student1.getTeacher().getName());
System.out.println(student2.getTeacher().getName());
}
}
class Teacher2 implements Cloneable {
private String name;
private int age;
public String get...
public void set...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student3 implements Cloneable {
private String name;
private int age;
private Teacher2 teacher;
public String get...
public void set...
@Override
public Object clone() throws CloneNotSupportedException {
Student3 student = (Student3) super.clone(); // 将 Teacher对象复制一份并重新set进来
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
输出结果:
拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey
teacher姓名Delacey更改前:
image.png
teacher姓名Jam更改后
image.png
原型模式结构图
image.png
原型模式主要包含如下三个角色:
Prototype:抽象原型类。声明克隆自身的接口。
ConcretePrototype:具体原型类。实现克隆的具体操作。
Client:客户类。让一个原型克隆自身,从而获得一个新的对象。
Java中任何实现了Cloneable接口的类都可以通过调用clone()方法来复制一份自身然后传给调用者。一般而言,clone()方法满足:
(1) 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象。
(2) 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
(3) 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
三、举例实现
复制简历
public class Resume implements Cloneable {
private String name;
private String company;
...//其他需要的信息
/**
* 构造函数:初始化简历赋值姓名
*/
public Resume(String name){
this.name = name;
}
/**
* @desc 设定工作经历
* @param company 所在公司
* @return void
*/
public void setWorkExperience(String company){
this.company = company;
}
...//其他需要设置的信息
/**
* 克隆该实例
*/
public Object clone(){
Resume resume = null;
try {
resume = (Resume) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return resume;
}
public void display(){
System.out.println("姓名:" + name);
System.out.println("公司:" + company);
}
}
客户端:
public class Client {
public static void main(String[] args) {
//原型A对象
Resume a = new Resume("小李子");
a.setWorkExperience( "XX科技有限公司");
//克隆B对象
Resume b = (Resume) a.clone();
//克隆C对象,并修改其中的数据
Resume c = (Resume) a.clone();
c.setWorkExperience("XX大学");
//输出A、B、C对象
System.out.println("----------------A--------------");
a.display();
System.out.println("----------------B--------------");
b.display();
System.out.println("----------------C--------------");
c.display();
/*
* 测试A==B?
* 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象
*/
System.out.print("A==B?");
System.out.println( a == b);
/*
* 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
*/
System.out.print("A.getClass()==B.getClass()?");
System.out.println(a.getClass() == b.getClass());
}
}
运行结果:
-----------------A-----------------
姓名:小李子
公司:XX科技有限公司
-----------------B-----------------
姓名:小李子
公司:XX科技有限公司
-----------------C-----------------
姓名:小李子
公司:XX大学
A==B? false
A.getClass()==B.getClass()?true
可以看到B完全复制了A的内容,C也复制了A的内容,但是C有自身的变化,但并没有影响到A和B。
四、原型模式的优缺点
优点:
1、如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2、可以使用深克隆保持对象的状态。
3、原型模式提供了简化的创建结构。
缺点:
1、在实现深克隆的时候可能需要比较复杂的代码。
2、需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
五、原型模式使用场景
1、如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
2、如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
3、需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
网友评论