Java?深入理解創(chuàng)建型設(shè)計(jì)模式之原型模式
1.思考問題
現(xiàn)在有一只羊 tom,姓名為: tom,年齡為:1,顏色為:白色,請(qǐng)編寫程序創(chuàng)建和 tom羊?qū)傩酝耆嗤?0只羊。
按照傳統(tǒng)的思路來,我們可能會(huì)按照下面的方式去寫。

那么這種寫法的優(yōu)缺點(diǎn)自然而然就出來了:
- 優(yōu)點(diǎn)是比較好理解,簡(jiǎn)單易操作。
- 缺點(diǎn)是在創(chuàng)建新的對(duì)象時(shí),總是需要重新獲取原始對(duì)象的屬性,如果創(chuàng)建的對(duì)象比較復(fù)雜時(shí),效率較低??偸切枰匦鲁跏蓟瘜?duì)象,而不是動(dòng)態(tài)地獲得對(duì)象運(yùn)行時(shí)的狀態(tài),不夠靈活。
- 改進(jìn)的思路分析:Java中Object類是所有類的根類,Object類提供了一個(gè) clone()方法,該方法可以將一個(gè)Java對(duì)象復(fù)制一份,但是需要實(shí)現(xiàn)clone的Java類必須要實(shí)現(xiàn)一個(gè)接口Cloneable,該接口表示該類能夠復(fù)制且具有復(fù)制的能力 => 原型模式。
2.什么是原型模式?
- 原型模式(Prototype模式)是指: 用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過拷貝這些原型,創(chuàng)建新的對(duì)象。
- 原型模式是一種創(chuàng)建型設(shè)計(jì)模式,允許一個(gè)對(duì)象再創(chuàng)建另外一個(gè)可定制的對(duì)象,無需知道如何創(chuàng)建的細(xì)節(jié)。
- 工作原理是: 通過將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過請(qǐng)求原型對(duì)象拷貝它們自己來實(shí)施創(chuàng)建,即對(duì)象.clone()。
原型模式相關(guān)的類圖如下:

3.克隆羊多莉案例代碼(淺拷貝)
這里由于我是新建的普通Java項(xiàng)目,并不是maven項(xiàng)目,所以沒法加lombok依賴。那么這里的構(gòu)造器、setter/getter方法顯得這個(gè)類的篇幅比較長。。。
package com.szh.prototype.shallowclone;
public class Sheep implements Cloneable {
private String name;
private Integer age;
private String color;
private Sheep friend;
public Sheep(String name, Integer age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public Sheep(String name, Integer age, String color, Sheep friend) {
this.name = name;
this.age = age;
this.color = color;
this.friend = friend;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Sheep getFriend() {
return friend;
}
public void setFriend(Sheep friend) {
this.friend = friend;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", friend=" + friend +
'}';
}
//克隆該實(shí)例,使用默認(rèn)的clone方法來完成
@Override
protected Object clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
package com.szh.prototype.shallowclone;
public class MainTest {
public static void main(String[] args) {
Sheep sheep = new Sheep("多莉",5,"黑白相間",new Sheep("喜羊羊",1,"白色"));
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
Sheep sheep5 = (Sheep) sheep.clone();
System.out.println(sheep + " sheep.friend.hashCode = " + sheep.getFriend().hashCode());
System.out.println(sheep2 + " sheep2.friend.hashCode = " + sheep2.getFriend().hashCode());
System.out.println(sheep3 + " sheep3.friend.hashCode = " + sheep3.getFriend().hashCode());
System.out.println(sheep4 + " sheep4.friend.hashCode = " + sheep4.getFriend().hashCode());
System.out.println(sheep5 + " sheep5.friend.hashCode = " + sheep5.getFriend().hashCode());
}
}
從上面的運(yùn)行結(jié)果中可以看到,Sheep類中的前三個(gè)成員屬性都可以成功的拷貝,但是最后一個(gè)friend,它表示羊的朋友(也是Sheep類型,就是引用類型了)。而當(dāng)我們拷貝完成之后,應(yīng)該來說都是不一樣的新的對(duì)象,但是它們中的friend屬性的hashCode居然是一樣的?。。?nbsp; 這里我們就要來聊一聊深拷貝和淺拷貝了。
淺拷貝:
- 對(duì)于數(shù)據(jù)類型是基本數(shù)據(jù)類型的成員變量,淺拷貝會(huì)直接進(jìn)行值傳遞,也就是將該屬性值復(fù)制一份給新的對(duì)象。
- 對(duì)于數(shù)據(jù)類型是引用數(shù)據(jù)類型的成員變量,比如說成員變量是某個(gè)數(shù)組、某個(gè)類對(duì)象等,那么淺拷貝會(huì)進(jìn)行引用傳遞,也就是只是將該成員變量的引用值(內(nèi)存地址〉復(fù)制一份給新的對(duì)象。因?yàn)閷?shí)際上兩個(gè)對(duì)象的該成員變量都指向同一個(gè)實(shí)例。在這種情況下,在一個(gè)對(duì)象中修改該成員變量會(huì)影響到另一個(gè)對(duì)象的該成員變量值。
- 前面我們克隆羊就是淺拷貝。淺拷貝是使用默認(rèn)的clone()方法來實(shí)現(xiàn):
sheep= (Sheep) super.clone();
深拷貝:
- 復(fù)制對(duì)象的所有基本數(shù)據(jù)類型的成員變量值。
- 為所有引用數(shù)據(jù)類型的成員變量申請(qǐng)存儲(chǔ)空間,并復(fù)制每個(gè)引用數(shù)據(jù)類型成員變量所引用的對(duì)象,直到該對(duì)象可達(dá)的所有對(duì)象。也就是說,對(duì)象進(jìn)行深拷貝要對(duì)整個(gè)對(duì)象(包括對(duì)象的引用類型)進(jìn)行拷貝。
- 深拷貝實(shí)現(xiàn)方式1: 重寫clone方法來實(shí)現(xiàn)深拷貝。
- 深拷貝實(shí)現(xiàn)方式2: 通過對(duì)象序列化實(shí)現(xiàn)深拷貝(推薦)。
4.深拷貝代碼案例
package com.szh.prototype.deepclone;
import java.io.Serializable;
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
@Override
public String toString() {
return "DeepCloneableTarget{" +
"cloneName='" + cloneName + '\'' +
", cloneClass='" + cloneClass + '\'' +
'}';
}
//因?yàn)樵擃惖膶傩?,都是String , 因此我們這里使用默認(rèn)的clone完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.szh.prototype.deepclone;
import java.io.*;
public class DeepPrototype implements Serializable, Cloneable {
public String name; //String 屬性
public DeepCloneableTarget deepCloneableTarget;// 引用類型
public DeepPrototype() {
super();
}
@Override
public String toString() {
return "DeepPrototype{" +
"name='" + name + '\'' +
", deepCloneableTarget=" + deepCloneableTarget +
'}';
}
//深拷貝 - 方式 1 使用clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
deep = super.clone();
DeepPrototype deepPrototype = (DeepPrototype) deep;
deepPrototype.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepPrototype;
}
//深拷貝 - 方式2 通過對(duì)象的序列化實(shí)現(xiàn) (推薦)
public Object deepClone() {
//創(chuàng)建流對(duì)象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepPrototype copyObj = (DeepPrototype) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//關(guān)閉流
try {
ois.close();
bis.close();
oos.close();
bos.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
package com.szh.prototype.deepclone;
public class MainTest {
public static void main(String[] args) throws Exception {
DeepPrototype prototype = new DeepPrototype();
prototype.name = "張起靈";
prototype.deepCloneableTarget = new DeepCloneableTarget("小哥","悶油瓶");
//方式1 完成深拷貝
DeepPrototype prototype2 = (DeepPrototype) prototype.clone();
System.out.println("方式1 完成深拷貝");
System.out.println("prototype.name = " + prototype.name + ", prototype.deepCloneableTarget = " + prototype.deepCloneableTarget);
System.out.println("prototype.deepCloneableTarget.hashCode = " + prototype.deepCloneableTarget.hashCode());
System.out.println("-------------------------------------------------------");
System.out.println("prototype2.name = " + prototype2.name + ", prototype2.deepCloneableTarget = " + prototype2.deepCloneableTarget);
System.out.println("prototype2.deepCloneableTarget.hashCode = " + prototype2.deepCloneableTarget.hashCode());
System.out.println("==============================================================================");
//方式2 完成深拷貝
DeepPrototype prototype3 = (DeepPrototype) prototype.deepClone();
System.out.println("方式2 完成深拷貝");
System.out.println("prototype.name = " + prototype.name + ", prototype.deepCloneableTarget = " + prototype.deepCloneableTarget);
System.out.println("prototype.deepCloneableTarget.hashCode = " + prototype.deepCloneableTarget.hashCode());
System.out.println("-------------------------------------------------------");
System.out.println("prototype3.name = " + prototype3.name + ", prototype3.deepCloneableTarget = " + prototype3.deepCloneableTarget);
System.out.println("prototype3.deepCloneableTarget.hashCode = " + prototype3.deepCloneableTarget.hashCode());
}
}
5.原型模式總結(jié)
- 創(chuàng)建新的對(duì)象比較復(fù)雜時(shí),可以利用原型模式簡(jiǎn)化對(duì)象的創(chuàng)建過程,同時(shí)也能夠提高效率。
- 不用重新初始化對(duì)象,而是動(dòng)態(tài)地獲得對(duì)象運(yùn)行時(shí)的狀態(tài)。
- 如果原始對(duì)象發(fā)生變化(增加或者減少屬性),其它克隆對(duì)象的也會(huì)發(fā)生相應(yīng)的變化,無需修改代碼。
- 在實(shí)現(xiàn)深克隆的時(shí)候可能需要比較復(fù)雜的代碼。
- 缺點(diǎn): 需要為每一個(gè)類配備一個(gè)克隆方法,這對(duì)全新的類來說不是很難,但對(duì)已有的類進(jìn)行改造時(shí),需要修改其源代碼,違背了ocp原則。
到此這篇關(guān)于Java 深入理解創(chuàng)建型設(shè)計(jì)模式之原型模式的文章就介紹到這了,更多相關(guān)Java 原型模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java AtomicInteger類使用方法實(shí)例講解
這篇文章主要介紹了Java AtomicInteger類使用方法實(shí)例講解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
springboot 自定義權(quán)限標(biāo)簽(tld),在freemarker引用操作
這篇文章主要介紹了springboot 自定義權(quán)限標(biāo)簽(tld),在freemarker引用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Java將word文件轉(zhuǎn)成pdf文件的操作方法
這篇文章主要介紹了Java將word文件轉(zhuǎn)成pdf文件的操作方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
如何基于java隨機(jī)獲取不重復(fù)數(shù)值
這篇文章主要介紹了如何基于java隨機(jī)獲取不重復(fù)數(shù)值,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Java面試題之HashMap 的 hash 方法原理是什么
那天,小二去蔚來面試,面試官老王一上來就問他:HashMap 的 hash 方法的原理是什么?當(dāng)時(shí)就把裸面的小二給蚌埠住了,這篇文章將詳細(xì)解答該題目2021-11-11
SpringBoot中整合Shiro實(shí)現(xiàn)權(quán)限管理的示例代碼
這篇文章主要介紹了SpringBoot中整合Shiro實(shí)現(xiàn)權(quán)限管理的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

