原型模式,顧名思義就是對(duì)現(xiàn)有的一個(gè)對(duì)象進(jìn)行復(fù)制克隆出一個(gè)全新的對(duì)象。被復(fù)制的對(duì)象就叫做原型對(duì)象,復(fù)制出來的克隆對(duì)象和原型對(duì)象具有相同的屬性和方法。
在一下情況我們一般會(huì)考慮使用原型模式來創(chuàng)建對(duì)象:
- 將對(duì)象交給外部處理的時(shí)候,為了防止外部操作對(duì)象修改數(shù)據(jù)導(dǎo)致其他地方受影響(實(shí)際傳遞的都是對(duì)象的引用,所以如果多個(gè)地方引用了該對(duì)象可能會(huì)造成不必要的麻煩),所以可以考慮使用原型模式來克隆出一個(gè)新的對(duì)象,及我們明確需要一個(gè)全新的對(duì)象。
- 如果在創(chuàng)建一個(gè)新對(duì)象的時(shí)候,初始化的資源非常的多,可以考慮使用原型模式來對(duì)一個(gè)現(xiàn)有的對(duì)象進(jìn)行克隆。
下面我們看看原型模式的UML圖:
- Prototype:可以是一個(gè)接口或者抽象類,聲明了clone的能力
- ConcretePrototype:實(shí)際上具備clone能力的類
- Client:使用clone功能的客戶端。

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();
}
}
}
運(yùn)行結(jié)果:
run ......
Person{name='zhangshan', age=34}designpattern.prototype.Person@1b6d3586
run ......
Person{name='zhangshan', age=34}designpattern.prototype.Person@4554617c
可以看到,克隆的是一個(gè)全新的對(duì)象,內(nèi)容和原型對(duì)象一模一樣。
我們先討論下兩個(gè)概率:淺拷貝和深拷貝。
淺拷貝:
在克隆一個(gè)對(duì)象的時(shí)候,如果對(duì)象中有引用型數(shù)據(jù),那么只會(huì)拷貝該數(shù)據(jù)的引用地址,并不會(huì)將引用數(shù)據(jù)對(duì)象全部拷貝。
深拷貝:
與淺拷貝不同,在拷貝引用型數(shù)據(jù)的時(shí)候,會(huì)完完全全的將引用型數(shù)據(jù)對(duì)象全部拷貝。
舉個(gè)例子看下它們的區(qū)別:
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("原數(shù)據(jù):" + data);
try {
Data clone = (Data) data.clone();
System.out.println("克隆數(shù)據(jù):" + clone);
System.out.println("-------------修改克隆數(shù)據(jù)---------------");
clone.setTitle("么么");
clone.addContent("么么噠");
System.out.println("修改后原數(shù)據(jù):" + data);
System.out.println("修改后克隆數(shù)據(jù):" + clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
打印結(jié)果:
原數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大]}
克隆數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大]}
-------------修改克隆數(shù)據(jù)---------------
修改后原數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大, 么么噠]}
修改后克隆數(shù)據(jù):Data{title='么么', contents=[呵呵大大, 么么噠]}
從結(jié)果看到,盡管克隆了一個(gè)新對(duì)象,但是原型對(duì)象的contents和克隆對(duì)象的contents的內(nèi)容是一樣的。
怎么解決呢?就需要使用到了深拷貝。
稍稍做一些修改
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 +
'}';
}
}
再看看打?。?/p>
原數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大]}
克隆數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大]}
-------------修改克隆數(shù)據(jù)---------------
修改后原數(shù)據(jù):Data{title='呵呵', contents=[呵呵大大]}
修改后克隆數(shù)據(jù):Data{title='么么', contents=[呵呵大大, 么么噠]}
原型對(duì)象和克隆對(duì)象的數(shù)據(jù)不一樣了。
所以在clone對(duì)象的時(shí)候,如果遇到了引用型數(shù)據(jù),需用調(diào)用引用型數(shù)據(jù)的clone()來重新克隆一份新的數(shù)據(jù),而不是引用地址。
我們也是建議如果需要clone對(duì)象的話盡量使用深拷貝,除非你確定你的對(duì)象都是數(shù)值型數(shù)據(jù)。
在java中如果想使用原型模式來創(chuàng)建對(duì)象,首先必須是要實(shí)現(xiàn)Cloneable接口,這只是一個(gè)標(biāo)記接口,里面啥也沒有,然后覆寫clone()方法即可。