注:示例来自《大话设计模式》
现有需求如下 要求有一个简历类 必须要有姓名 可以设置性别和年龄 可以设置工作经历 最终我需要写三份简历 代码初步实现如下
简历类
package Test09;
//简历
public class Resume {
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name)
{
this.name = name;
}
//设置个人信息
public void SetPersonalInfo(String sex, String age)
{
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(name+" "+sex+" "+age);
System.out.println(timeArea+" "+company);
}
}
客户端代码
package Test09;
public class Program {
public static void main(String[] args) {
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = new Resume("大鸟");
b.SetPersonalInfo("男", "29");
b.SetWorkExperience("1998-2000", "XX公司");
Resume c = new Resume("大鸟");
c.SetPersonalInfo("男", "29");
c.SetWorkExperience("1998-2000", "XX公司");
a.Display();
b.Display();
c.Display();
}
}
上面的代码 三份简历需要三次实例化 很麻烦 我们看看下面的写法
客户端代码
package Test09;
public class Program {
public static void main(String[] args) {
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = a;
Resume c = a;
a.Display();
b.Display();
c.Display();
}
}
上面的代码 其实是传引用 而不是传值 这样做就如同是在b纸张和c纸张上写着简历在a处一样 没有实际的内容
接下来采用原型模式(Clone克隆)进行重构 代码如下
简历类
package Test09;
//简历
public class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private String timeArea;
private String company;
public Resume(String name)
{
this.name = name;
}
//设置个人信息
public void SetPersonalInfo(String sex, String age)
{
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(name+" "+sex+" "+age);
System.out.println(timeArea+" "+company);
}
public Object Clone() throws CloneNotSupportedException
{
return (Object)super.clone();
}
}
客户端代码
package Test09;
public class Program {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetPersonalInfo("男", "24");
a.Display();
b.Display();
c.Display();
}
}
clone()方法是这样 如果字段是值类型的 则对该字段执行逐位复制 如果字段是引用类型 则复制引用但不复制引用的对象 因此 原始对象及其复本引用同一对象 就是说如果简历类中有对象引用 那么引用的对象数据是不会被克隆过来的
例如我们单独写个工作经历类 里面有时间区间和公司名称属性 代码如下
工作经历类
package Test09;
//工作经历
public class WorkExperience {
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
简历类
package Test09;
//简历
public class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private WorkExperience work;
public Resume(String name)
{
this.name = name;
work = new WorkExperience();
}
//设置个人信息
public void SetPersonalInfo(String sex, String age)
{
this.sex = sex;
this.age = age;
}
//设置工作经历
public void SetWorkExperience(String workDate, String company)
{
work.setWorkDate(workDate);
work.setCompany(company);
}
//显示
public void Display()
{
System.out.println(name+" "+sex+" "+age);
System.out.println(work.getWorkDate()+" "+work.getCompany());
}
public Object Clone() throws CloneNotSupportedException
{
return (Object)super.clone();
}
}
客户端代码
package Test09;
public class Program {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetPersonalInfo("男", "24");
c.SetWorkExperience("1998-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
}
}
可以看到我给a b c三个引用设置工作经历 三个引用都指向了同一个对象 这叫做潜复制 被复制对象的所有变量都含有与原来的对象相同的值 而所有的对其他对象的引用都仍然指向原来的对象
但我们可能更需要这样的一种需求 把要复制的对象所引用的对象都复制一遍 比如刚才的例子 我们希望是a b c三个引用的对象都是不同的 即深复制 深复制把引用对象的变量指向复制过的新对象 而不是原有的被引用的对象 下面是深复制实现的代码
工作经历类
package Test09;
//工作经历
public class WorkExperience implements Cloneable {
private String workDate;
private String company;
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = workDate;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public Object Clone() throws CloneNotSupportedException
{
return (Object)super.clone();
}
}
简历类
package Test09;
//简历
public class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private WorkExperience work;
public Resume(String name)
{
this.name = name;
work = new WorkExperience();
}
private Resume(WorkExperience work) throws CloneNotSupportedException
{
this.work = (WorkExperience)work.Clone();
}
//设置个人信息
public void SetPersonalInfo(String sex, String age)
{
this.sex = sex;
this.age = age;
}
//设置工作经历
public void SetWorkExperience(String workDate, String company)
{
work.setWorkDate(workDate);
work.setCompany(company);
}
//显示
public void Display()
{
System.out.println(name+" "+sex+" "+age);
System.out.println(work.getWorkDate()+" "+work.getCompany());
}
public Object Clone() throws CloneNotSupportedException
{
Resume obj = new Resume(this.work);
obj.name = this.name;
obj.sex = this.sex;
obj.age = this.age;
return obj;
}
}
客户端代码
package Test09;
public class Program {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a = new Resume("大鸟");
a.SetPersonalInfo("男", "29");
a.SetWorkExperience("1998-2000", "XX公司");
Resume b = (Resume)a.Clone();
b.SetWorkExperience("1998-2006", "YY企业");
Resume c = (Resume)a.Clone();
c.SetPersonalInfo("男", "24");
c.SetWorkExperience("1998-2003", "ZZ企业");
a.Display();
b.Display();
c.Display();
}
}
以上为深复制实现
原型模式 用原型实例指定创建对象的种类 并且通过拷贝这些原型创建新的对象
原型模式其实就是从一个对象再创建另外一个可定制的对象 而且不需知道任何创建的细节 实现Cloneable这个接口就可以完成原型模式
一般在初始化的信息不发生变化的情况下 克隆是最好的办法 既隐藏了对象创建的细节 又对性能是大大的提高 它等于是不用重新初始化对象 而是动态地获得对象运行时的状态
缺点 原型模式无法和单例模式组合使用 使用原型模式时不能有final对象 浅复制