一、問題的提出
在應(yīng)用程序中,有些對象比較復(fù)雜,其創(chuàng)建過程過于復(fù)雜,而且我們又需要頻繁的利用該對象,如果這個時候我們按照常規(guī)思維new該對象,那么務(wù)必會帶來非常多的麻煩,這個時候我們就希望可以利用一個已有的對象來不斷對他進(jìn)行復(fù)制就好了。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫,以此來減少數(shù)據(jù)庫調(diào)用。
二、原型模式
能夠解決以上問題的模式稱為原型模式。
原型模式:用原型實例指定創(chuàng)建對象的種類,并且通過復(fù)制這些原型創(chuàng)建新的對象。
在原型模式中可以利用一個原型對象來指明要創(chuàng)建對象的類型,然后通過復(fù)制這個對象的方法來獲得與該對象一模一樣的對象實例。這就是原型模式的設(shè)計目的。
在原型模式中,所發(fā)動創(chuàng)建的對象通過請求原型對象來拷貝原型對象自己來實現(xiàn)創(chuàng)建過程,當(dāng)然所發(fā)動創(chuàng)建的對象需要知道原型對象的類型。也就是說所發(fā)動創(chuàng)建的對象只需要知道原型對象的類型就可以獲得更多的原型實例對象,至于這些原型對象時如何創(chuàng)建的根本不需要關(guān)心。
Tips:淺拷貝,深拷貝
先理解一下引用拷貝和對象拷貝
1、引用拷貝
Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);
輸出結(jié)果:
blog.Teacher@355da254
blog.Teacher@355da254
結(jié)果分析:由輸出結(jié)果可以看出,它們的地址值是相同的,那么它們肯定是同一個對象。teacher和otherteacher的只是引用而已,他們都指向了一個相同的對象Teacher(“Taylor”,26)。 這就叫做引用拷貝。
2、對象拷貝
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
輸出結(jié)果:
blog.Teacher@355da254
blog.Teacher@4dc63996
結(jié)果分析:由輸出結(jié)果可以看出,它們的地址是不同的,也就是說創(chuàng)建了新的對象, 而不是把原對象的地址賦給了一個新的引用變量,這就叫做對象拷貝。
深拷貝和淺拷貝都是對象拷貝
淺拷貝:對于基本類型的數(shù)據(jù)會進(jìn)行拷貝,而對其他對象的引用仍然指向原來的對象。
深拷貝:基本類型和其他對象都會進(jìn)行拷貝。
舉個栗子:
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;
}
}
輸出結(jié)果:
拷貝后
Dream
18
Delacey
29
修改老師的信息后-------------
Jam
Jam
結(jié)果分析: 兩個引用student1和student2指向不同的兩個對象,但是兩個引用student1和student2中的兩個teacher引用指向的是同一個對象,所以說明是淺拷貝。
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對象復(fù)制一份并重新set進(jìn)來
student.setTeacher((Teacher2) student.getTeacher().clone());
return student;
}
}
輸出結(jié)果:
拷貝后
Dream
18
Delacey
29
修改老師的信息后-------------
Jam
Delacey
teacher姓名Delacey更改前:
teacher姓名Jam更改后
原型模式結(jié)構(gòu)圖
原型模式主要包含如下三個角色:
Prototype:抽象原型類。聲明克隆自身的接口。
ConcretePrototype:具體原型類。實現(xiàn)克隆的具體操作。
Client:客戶類。讓一個原型克隆自身,從而獲得一個新的對象。
Java中任何實現(xiàn)了Cloneable接口的類都可以通過調(diào)用clone()方法來復(fù)制一份自身然后傳給調(diào)用者。一般而言,clone()方法滿足:
(1) 對任何的對象x,都有x.clone() !=x,即克隆對象與原對象不是同一個對象。
(2) 對任何的對象x,都有x.clone().getClass()==x.getClass(),即克隆對象與原對象的類型一樣。
(3) 如果對象x的equals()方法定義恰當(dāng),那么x.clone().equals(x)應(yīng)該成立。
三、舉例實現(xiàn)
復(fù)制簡歷
public class Resume implements Cloneable {
private String name;
private String company;
...//其他需要的信息
/**
* 構(gòu)造函數(shù):初始化簡歷賦值姓名
*/
public Resume(String name){
this.name = name;
}
/**
* @desc 設(shè)定工作經(jīng)歷
* @param company 所在公司
* @return void
*/
public void setWorkExperience(String company){
this.company = company;
}
...//其他需要設(shè)置的信息
/**
* 克隆該實例
*/
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對象,并修改其中的數(shù)據(jù)
Resume c = (Resume) a.clone();
c.setWorkExperience("XX大學(xué)");
//輸出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());
}
}
運行結(jié)果:
-----------------A-----------------
姓名:小李子
公司:XX科技有限公司
-----------------B-----------------
姓名:小李子
公司:XX科技有限公司
-----------------C-----------------
姓名:小李子
公司:XX大學(xué)
A==B? false
A.getClass()==B.getClass()?true
可以看到B完全復(fù)制了A的內(nèi)容,C也復(fù)制了A的內(nèi)容,但是C有自身的變化,但并沒有影響到A和B。
四、原型模式的優(yōu)缺點
優(yōu)點:
1、如果創(chuàng)建新的對象比較復(fù)雜時,可以利用原型模式簡化對象的創(chuàng)建過程,同時也能夠提高效率。
2、可以使用深克隆保持對象的狀態(tài)。
3、原型模式提供了簡化的創(chuàng)建結(jié)構(gòu)。
缺點:
1、在實現(xiàn)深克隆的時候可能需要比較復(fù)雜的代碼。
2、需要為每一個類配備一個克隆方法,而且這個克隆方法需要對類的功能進(jìn)行通盤考慮,這對全新的類來說不是很難,但對已有的類進(jìn)行改造時,不一定是件容易的事,必須修改其源代碼,違背了“開閉原則”。
五、原型模式使用場景
1、如果創(chuàng)建新對象成本較大,我們可以利用已有的對象進(jìn)行復(fù)制來獲得。
2、如果系統(tǒng)要保存對象的狀態(tài),而對象的狀態(tài)變化很小,或者對象本身占內(nèi)存不大的時候,也可以使用原型模式配合備忘錄模式來應(yīng)用。相反,如果對象的狀態(tài)變化很大,或者對象占用的內(nèi)存很大,那么采用狀態(tài)模式會比原型模式更好。
3、需要避免使用分層次的工廠類來創(chuàng)建分層次的對象,并且類的實例對象只有一個或很少的幾個組合狀態(tài),通過復(fù)制原型對象得到新實例可能比使用構(gòu)造函數(shù)創(chuàng)建一個新實例更加方便。