原型模式,顧名思義就是對現有的一個對象進行復制克隆出一個全新的對象。被復制的對象就叫做原型對象,復制出來的克隆對象和原型對象具有相同的屬性和方法。
在一下情況我們一般會考慮使用原型模式來創建對象:
- 將對象交給外部處理的時候,為了防止外部操作對象修改數據導致其他地方受影響(實際傳遞的都是對象的引用,所以如果多個地方引用了該對象可能會造成不必要的麻煩),所以可以考慮使用原型模式來克隆出一個新的對象,及我們明確需要一個全新的對象。
- 如果在創建一個新對象的時候,初始化的資源非常的多,可以考慮使用原型模式來對一個現有的對象進行克隆。
下面我們看看原型模式的UML圖:
- Prototype:可以是一個接口或者抽象類,聲明了clone的能力
- ConcretePrototype:實際上具備clone能力的類
- Client:使用clone功能的客戶端。
prototype.png
public class Person implements Cloneable{
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
public void run() {
System.out.println("run ......");
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}' + super.toString();
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person("zhangshan",34);
p.run();
System.out.println(p);
try {
Person pClone = (Person) p.clone();
pClone.run();
System.out.println(pClone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
運行結果:
run ......
Person{name='zhangshan', age=34}designpattern.prototype.Person@1b6d3586
run ......
Person{name='zhangshan', age=34}designpattern.prototype.Person@4554617c
可以看到,克隆的是一個全新的對象,內容和原型對象一模一樣。
我們先討論下兩個概率:淺拷貝和深拷貝。
淺拷貝:
在克隆一個對象的時候,如果對象中有引用型數據,那么只會拷貝該數據的引用地址,并不會將引用數據對象全部拷貝。
深拷貝:
與淺拷貝不同,在拷貝引用型數據的時候,會完完全全的將引用型數據對象全部拷貝。
舉個例子看下它們的區別:
class Data implements Cloneable{
private String title;
private ArrayList<String> contents = new ArrayList<>();
public void setTitle(String title) {
this.title = title;
}
public void addContent(String content) {
contents.add(content);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Data{" +
"title='" + title + '\'' +
", contents=" + contents +
'}';
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
data.setTitle("呵呵");
data.addContent("呵呵大大");
System.out.println("原數據:" + data);
try {
Data clone = (Data) data.clone();
System.out.println("克隆數據:" + clone);
System.out.println("-------------修改克隆數據---------------");
clone.setTitle("么么");
clone.addContent("么么噠");
System.out.println("修改后原數據:" + data);
System.out.println("修改后克隆數據:" + clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
打印結果:
原數據:Data{title='呵呵', contents=[呵呵大大]}
克隆數據:Data{title='呵呵', contents=[呵呵大大]}
-------------修改克隆數據---------------
修改后原數據:Data{title='呵呵', contents=[呵呵大大, 么么噠]}
修改后克隆數據:Data{title='么么', contents=[呵呵大大, 么么噠]}
從結果看到,盡管克隆了一個新對象,但是原型對象的contents和克隆對象的contents的內容是一樣的。
怎么解決呢?就需要使用到了深拷貝。
稍稍做一些修改
class Data implements Cloneable{
private String title;
private ArrayList<String> contents = new ArrayList<>();
public void setTitle(String title) {
this.title = title;
}
public void addContent(String content) {
contents.add(content);
}
@Override
protected Object clone() throws CloneNotSupportedException {
Data data = (Data) super.clone();
data.contents = (ArrayList<String>) contents.clone();
return data;
}
@Override
public String toString() {
return "Data{" +
"title='" + title + '\'' +
", contents=" + contents +
'}';
}
}
再看看打印:
原數據:Data{title='呵呵', contents=[呵呵大大]}
克隆數據:Data{title='呵呵', contents=[呵呵大大]}
-------------修改克隆數據---------------
修改后原數據:Data{title='呵呵', contents=[呵呵大大]}
修改后克隆數據:Data{title='么么', contents=[呵呵大大, 么么噠]}
原型對象和克隆對象的數據不一樣了。
所以在clone對象的時候,如果遇到了引用型數據,需用調用引用型數據的clone()來重新克隆一份新的數據,而不是引用地址。
我們也是建議如果需要clone對象的話盡量使用深拷貝,除非你確定你的對象都是數值型數據。
在java中如果想使用原型模式來創建對象,首先必須是要實現Cloneable接口,這只是一個標記接口,里面啥也沒有,然后覆寫clone()方法即可。