原文地址:LoveDev
原型模式是創(chuàng)建型模式的一種爵憎,其特點(diǎn)在于通過“復(fù)制”一個(gè)已經(jīng)存在的實(shí)例來返回新的實(shí)例筑悴,而不是新建實(shí)例人弓。被復(fù)制的實(shí)例就是我們所稱的“原型”鹰椒,這個(gè)原型是可定制的
使用場景:
- 通過 new 產(chǎn)生一個(gè)對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備和訪問權(quán)限
- 一個(gè)對象需要提供給其他對象訪問锡移,每個(gè)調(diào)用者都有可能修改其屬性,可以原型模式拷貝多個(gè)對象供調(diào)用者使用吹零,即保護(hù)性拷貝
uml 看起來是不是特別簡單罩抗,用起來其實(shí)也很簡單,Object
類已經(jīng)提供了一個(gè)clone()
方法灿椅,查看源碼可以得知,想要使用該方法钞支,還要實(shí)現(xiàn)一個(gè)標(biāo)識(shí)接口Cloneable 茫蛹,clone()
源碼:
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone();
}
Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口烁挟,這種空接口也稱為標(biāo)識(shí)接口婴洼,標(biāo)識(shí)接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實(shí)現(xiàn)類是否具有某個(gè)功能撼嗓,如是否支持克隆柬采、是否支持序列化等
再來看看超級(jí)簡單的示例代碼:
具體原型類:
public class ConcretePrototype implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "ConcretePrototype{" +
"name='" + name + '\'' +
", person=" + person +
'}';
}
}
看著有沒有特別的簡單方便,但是這里面有一個(gè)坑在且警,這就牽扯到淺拷貝和深拷貝粉捻,上述的原型模式就是淺拷貝,也稱為影子拷貝斑芜,如果需要拷貝的類中全部都是基礎(chǔ)類型的屬性肩刃,淺拷貝也是沒有問題的,但是有引用類型的屬性杏头,就會(huì)出現(xiàn)問題了盈包,出現(xiàn)問題的示例代碼:
具體原型類:
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
// 具體原型類
public class ConcretePrototype implements Cloneable {
private String name;
private Person person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "ConcretePrototype{" +
"name='" + name + '\'' +
", person=" + person +
'}';
}
}
Client類:
public class PrototypeActivity extends Activity {
@BindView(R.id.prototype_text)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_prototype);
ButterKnife.bind(this);
}
@OnClick(R.id.prototype)
public void prototype_click() {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.setName("Kevin");
Person person = new Person();
person.setName("Person");
person.setAge("10");
concretePrototype.setPerson(person);
StringBuilder stringBuilder = new StringBuilder("concretePrototype:" + concretePrototype.toString());
try {
ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
clone.setName("LoveDev");
stringBuilder.append("\nclone: ").append(clone.toString()); //第一次修改,正常
Person newPerson = clone.getPerson();
newPerson.setName("newPerson");
newPerson.setAge("20");
clone.setPerson(newPerson);
stringBuilder.append("\n\n\nconcretePrototype: ").append(concretePrototype.toString());
stringBuilder.append("\nclone: ").append(clone.toString()); //第二次修改醇王,被拷貝對象同時(shí)被修改
stringBuilder.append("\n\n\n 對比兩個(gè)類中的 Person 字段是否相同:").append(concretePrototype.getPerson() == clone.getPerson());
mTextView.setText(stringBuilder);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
第一次修改的時(shí)候呢燥,只改了具體原型類中的String
類型,打印結(jié)果沒有是問題的寓娩,只修改了克隆后的類叛氨,第二次修改Person
字段滥朱,再次打印的時(shí)候,發(fā)現(xiàn)原型類已經(jīng)被修改了力试,導(dǎo)致這個(gè)問題的原因是因?yàn)闇\拷貝只是拷貝了引用地址徙邻,兩個(gè)對象指定的是同一內(nèi)存地址,從打印結(jié)果就可以看的出來,要解決這個(gè)問題就要采用深拷貝進(jìn)行克隆撩银,在克隆對象時(shí)腺逛,對于引用類型的字段也要采用克隆的形式,修改后的示例代碼:
public class ConcretePrototype implements Cloneable {
private String name;
private Person person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
protected Object clone() throws CloneNotSupportedException {
ConcretePrototype concretePrototype = new ConcretePrototype();
concretePrototype.name = this.name;
concretePrototype.person = (Person) this.person.clone();
return concretePrototype;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"name='" + name + '\'' +
", person=" + person +
'}';
}
}
再次執(zhí)行帅容,可以發(fā)現(xiàn)問題得以解決,原型模式一個(gè)很重要的用途就是保護(hù)性拷貝伍伤,再給其他模塊提供接口訪問一個(gè)不可修改的對象時(shí)并徘,為了防止該模塊負(fù)責(zé)人出于某種原因而修改該對象時(shí),就要用原型模式進(jìn)行保護(hù)性拷貝
優(yōu)點(diǎn):
看了很多的文章都說原型模式效率搞扰魂,下面分別是原型模式和直接new在10000次for循環(huán)中創(chuàng)建對象的內(nèi)存消耗和耗時(shí):
直接new對象麦乞,執(zhí)行耗時(shí)為23毫秒,內(nèi)存占用增加909K劝评,對比圖:
原型模式姐直,執(zhí)行耗時(shí)為44毫秒,內(nèi)存占用增加876K蒋畜,對比圖:
從這些數(shù)據(jù)來看声畏,原型模式增加了將近一倍的耗時(shí),內(nèi)存占用并沒有少很多姻成,這還是建立在不太準(zhǔn)確的測試數(shù)據(jù)上插龄,個(gè)人認(rèn)為原型模式在效率方面比直接 new 對象并沒有提高,不過還有其他的優(yōu)點(diǎn)在:
- 原型模式提供了簡化的創(chuàng)建結(jié)構(gòu)科展,工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級(jí)結(jié)構(gòu)相同的工廠等級(jí)結(jié)構(gòu)均牢,而原型模式就不需要這樣,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實(shí)現(xiàn)的辛润,無須專門的工廠類來創(chuàng)建產(chǎn)品
- 可以利用深拷貝實(shí)現(xiàn)保護(hù)性拷貝膨处,可實(shí)現(xiàn)撤銷操作
缺點(diǎn):
- 想要使用原型模式就要在類中實(shí)現(xiàn)克隆方法,修改類的源碼砂竖,違背了“開閉原則”
- 深拷貝是需要編寫大量的代碼真椿,多層對象嵌套時(shí),每層對象都要支持深拷貝