一、概念
原型模式是使用原型实例创建对象的种类,并且通过拷贝这些原型创建新的对象。其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
比如一个在一个系统中,需要每个人填写自己的简历信息,简历的模板是一样的,只是简历中的基本信息是不同的,这时候就用户在写简历时不需要重新创建新的简历,可以复制一份已经写好的简历来把其中的信息修改即可。因此原型模式就是我们平常用的非常多的也是非常喜欢的Ctrl+C,Ctrl+V。
二、结构图
三、实例
实现简历的克隆。
3.1、用普通方法的实现克隆
1、简历类
/**
* <p>标题: ConcretePrototype</p>
* <p>描述: 简历类,使用最普通的方法实现克隆,不限制语言。</p>
*/
public class Resume{
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
//设置个人信息
public void setPersonalInfo(String name,String sex,String age){
this.name=name;
this.sex=sex;
this.age=age;
}
//设置工作经历
public void setWorkExperience(String timeArea,String company){
this.timeArea=timeArea;
this.company=company;
}
//显示
public void display(){
System.out.println(this.name+" "+this.age);
System.out.println(this.timeArea+" "+this.company);
}
//克隆
public Resume clone(){
Resume resume=new Resume();
resume.setName(this.name);
resume.setAge(this.age);
resume.setSex(this.sex);
resume.setTimeArea(this.timeArea);
resume.setCompany(this.company);
return resume;
}
2、测试类
public static void main(String[] args) {
Resume resume1=new Resume();
resume1.setPersonalInfo("笑笑", "女", "18");
resume1.setWorkExperience("2012-2016", "a公司");
Resume resume1Copy=resume1.clone();
resume1Copy.setPersonalInfo("萌萌", "女", "19");
resume1.display();
resume1Copy.display();
}
3、运行结果
笑笑 18
2012-2016 a公司
萌萌 19
2012-2016 a公司
3.2、用java提供的clone()实现克隆
/**
* <p>标题: ConcretePrototype</p>
* <p>描述: 简历类,使用java语言中Cloneable提供的clone()来实现克隆</p>
*/
public class Resume2 implements Cloneable{
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
//设置个人信息
public void setPersonalInfo(String name,String sex,String age){
this.name=name;
this.sex=sex;
this.age=age;
}
//设置工作经历
public void setWorkExperience(String timeArea,String company){
this.timeArea=timeArea;
this.company=company;
}
//显示
public void display(){
System.out.println(this.name+" "+this.age);
System.out.println(this.timeArea+" "+this.company);
}
public Resume2 clone(){
Resume2 resume = null;
try {
resume = (Resume2) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return resume;
}
}
2、测试类
public static void main(String[] args) {
Resume2 resume2=new Resume2();
resume2.setPersonalInfo("笑笑", "女", "18");
resume2.setWorkExperience("2012-2016", "b公司");
Resume2 resume2Copy=resume2.clone();
resume2Copy.setPersonalInfo("萌萌", "女", "19");
resume2.display();
resume2Copy.display();
}
3、运行结果
笑笑 18
2012-2016 b公司
萌萌 19
2012-2016 b公司
3.3、浅克隆
学java的都知道,java变量类型有值类型和引用类型,在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向的是相同的内存的地址即同一个实体。3.2中所引用的Cloneable中的clone()就是浅复制。
下面通过简历类测试:
1、将简历中的工作经历提出一个单独类
/**
* <p>标题: WorkExperience</p>
* <p>描述: 工作经历</p>
*/
public class WorkExperience{
private String timeArea;
private String company;
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
2、简历类
/**
* <p>标题: ConcretePrototype</p>
* <p>描述: 简历类,把工作经历单独提出一个类,使用Cloneable提供的clone()实现克隆</p>
*/
public class Resume3 implements Cloneable{
private String name;
private String sex;
private String age;
private WorkExperience workExperience;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
//设置个人信息
public void setPersonalInfo(String name,String sex,String age){
this.name=name;
this.sex=sex;
this.age=age;
}
//设置工作经历
public WorkExperience getWorkExperience() {
return workExperience;
}
public void setWorkExperience(WorkExperience workExperience) {
this.workExperience = workExperience;
}
//显示
public void display(){
System.out.println(this.name+" "+this.age);
System.out.println(this.workExperience.getTimeArea()+" "+this.workExperience.getCompany());
}
public Resume3 clone(){
Resume3 resume = null;
try {
resume = (Resume3) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return resume;
}
}
3、测试类
public static void main(String[] args) {
Resume3 resume3=new Resume3();
WorkExperience workExperience=new WorkExperience();
workExperience.setCompany("c公司");
workExperience.setTimeArea("2013-2017");
resume3.setPersonalInfo("小李", "男", "24");
resume3.setWorkExperience(workExperience);
Resume3 resume3Copy=resume3.clone();
//使用==来测试两个对象是否是同一个对象
System.out.println(resume3==resume3Copy);
System.out.println(resume3.getWorkExperience()==resume3Copy.getWorkExperience());
}
4、运行结果
false
true
可以看出简历类被复制了一份,resume3和resume3Copy指向的是不同的内存,是两个对象,但工作经历是相同的,是指向的同一块内存,因此Cloneable中的clone()是浅克隆。
如果想实现深克隆,即无论是值类型还是引用类型都复制一遍,可通过序列化(Serialization)等方式来实现,请看3.4深克隆。
3.3、深克隆
通过序列化(Serialization)等方式来实现。序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
1、工作经历类
public class WorkExperience implements Serializable{
private String timeArea;
private String company;
public String getTimeArea() {
return timeArea;
}
public void setTimeArea(String timeArea) {
this.timeArea = timeArea;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
2、简历类
public class Resume4 implements Serializable{
private String name;
private String sex;
private String age;
private WorkExperience workExperience;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
//设置个人信息
public void setPersonalInfo(String name,String sex,String age){
this.name=name;
this.sex=sex;
this.age=age;
}
//设置工作经历
public WorkExperience getWorkExperience() {
return workExperience;
}
public void setWorkExperience(WorkExperience workExperience) {
this.workExperience = workExperience;
}
//显示
public void display(){
System.out.println(this.name+" "+this.age);
System.out.println(this.workExperience.getTimeArea()+" "+this.workExperience.getCompany());
}
public Resume4 clone() {
Resume4 resume4=null;
try {
//将对象写入流中
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bais);
resume4=(Resume4)ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return resume4;
}
}
3、测试
public static void main(String[] args) {
Resume4 resume4=new Resume4();
WorkExperience workExperience4=new WorkExperience();
workExperience4.setCompany("c公司");
workExperience4.setTimeArea("2013-2017");
resume4.setPersonalInfo("小李", "男", "24");
resume4.setWorkExperience(workExperience4);
Resume4 resume4Copy=resume4.clone();
System.out.println(resume4==resume4Copy);
System.out.println(resume4.getWorkExperience()==resume4Copy.getWorkExperience());
}
4、运行结果
false
false
运行结果两个都是false,可见实现了深克隆。
四、总结
1、优点
(1)、当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高新实例的创建效率。
(2)、扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品对原有系统都没有任何影响。
2、缺点
(1)、 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
(2)、 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
3、使用场景
(1)、 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
(2)、 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
(3)、需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
参考:
https://www.cnblogs.com/lfxiao/p/6812835.html
《大话设计模式》