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

那么這種寫(xiě)法的優(yōu)缺點(diǎn)自然而然就出來(lái)了:
- 優(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類(lèi)是所有類(lèi)的根類(lèi),Object類(lèi)提供了一個(gè) clone()方法,該方法可以將一個(gè)Java對(duì)象復(fù)制一份,但是需要實(shí)現(xiàn)clone的Java類(lèi)必須要實(shí)現(xiàn)一個(gè)接口Cloneable,該接口表示該類(lèi)能夠復(fù)制且具有復(fù)制的能力 => 原型模式。
2.什么是原型模式?
- 原型模式(Prototype模式)是指: 用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并且通過(guò)拷貝這些原型,創(chuàng)建新的對(duì)象。
- 原型模式是一種創(chuàng)建型設(shè)計(jì)模式,允許一個(gè)對(duì)象再創(chuàng)建另外一個(gè)可定制的對(duì)象,無(wú)需知道如何創(chuàng)建的細(xì)節(jié)。
- 工作原理是: 通過(guò)將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過(guò)請(qǐng)求原型對(duì)象拷貝它們自己來(lái)實(shí)施創(chuàng)建,即對(duì)象.clone()。
原型模式相關(guān)的類(lèi)圖如下:

3.克隆羊多莉案例代碼(淺拷貝)
這里由于我是新建的普通Java項(xiàng)目,并不是maven項(xiàng)目,所以沒(méi)法加lombok依賴(lài)。那么這里的構(gòu)造器、setter/getter方法顯得這個(gè)類(lèi)的篇幅比較長(zhǎng)。。。
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方法來(lái)完成
@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類(lèi)中的前三個(gè)成員屬性都可以成功的拷貝,但是最后一個(gè)friend,它表示羊的朋友(也是Sheep類(lèi)型,就是引用類(lèi)型了)。而當(dāng)我們拷貝完成之后,應(yīng)該來(lái)說(shuō)都是不一樣的新的對(duì)象,但是它們中的friend屬性的hashCode居然是一樣的!??! 這里我們就要來(lái)聊一聊深拷貝和淺拷貝了。
淺拷貝:
- 對(duì)于數(shù)據(jù)類(lèi)型是基本數(shù)據(jù)類(lèi)型的成員變量,淺拷貝會(huì)直接進(jìn)行值傳遞,也就是將該屬性值復(fù)制一份給新的對(duì)象。
- 對(duì)于數(shù)據(jù)類(lèi)型是引用數(shù)據(jù)類(lèi)型的成員變量,比如說(shuō)成員變量是某個(gè)數(shù)組、某個(gè)類(lèi)對(duì)象等,那么淺拷貝會(huì)進(jìn)行引用傳遞,也就是只是將該成員變量的引用值(內(nèi)存地址〉復(fù)制一份給新的對(duì)象。因?yàn)閷?shí)際上兩個(gè)對(duì)象的該成員變量都指向同一個(gè)實(shí)例。在這種情況下,在一個(gè)對(duì)象中修改該成員變量會(huì)影響到另一個(gè)對(duì)象的該成員變量值。
- 前面我們克隆羊就是淺拷貝。淺拷貝是使用默認(rèn)的clone()方法來(lái)實(shí)現(xiàn):
sheep= (Sheep) super.clone();
深拷貝:
- 復(fù)制對(duì)象的所有基本數(shù)據(jù)類(lèi)型的成員變量值。
- 為所有引用數(shù)據(jù)類(lèi)型的成員變量申請(qǐng)存儲(chǔ)空間,并復(fù)制每個(gè)引用數(shù)據(jù)類(lèi)型成員變量所引用的對(duì)象,直到該對(duì)象可達(dá)的所有對(duì)象。也就是說(shuō),對(duì)象進(jìn)行深拷貝要對(duì)整個(gè)對(duì)象(包括對(duì)象的引用類(lèi)型)進(jìn)行拷貝。
- 深拷貝實(shí)現(xiàn)方式1: 重寫(xiě)clone方法來(lái)實(shí)現(xiàn)深拷貝。
- 深拷貝實(shí)現(xiàn)方式2: 通過(guò)對(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)樵擃?lèi)的屬性,都是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;// 引用類(lèi)型
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 通過(guò)對(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)建過(guò)程,同時(shí)也能夠提高效率。
- 不用重新初始化對(duì)象,而是動(dòng)態(tài)地獲得對(duì)象運(yùn)行時(shí)的狀態(tài)。
- 如果原始對(duì)象發(fā)生變化(增加或者減少屬性),其它克隆對(duì)象的也會(huì)發(fā)生相應(yīng)的變化,無(wú)需修改代碼。
- 在實(shí)現(xiàn)深克隆的時(shí)候可能需要比較復(fù)雜的代碼。
- 缺點(diǎn): 需要為每一個(gè)類(lèi)配備一個(gè)克隆方法,這對(duì)全新的類(lèi)來(lái)說(shuō)不是很難,但對(duì)已有的類(lèi)進(jìn)行改造時(shí),需要修改其源代碼,違背了ocp原則。
到此這篇關(guān)于Java 深入理解創(chuàng)建型設(shè)計(jì)模式之原型模式的文章就介紹到這了,更多相關(guān)Java 原型模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot bean掃描路徑的實(shí)現(xiàn)
這篇文章主要介紹了springboot bean掃描路徑的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01
Java框架入門(mén)之簡(jiǎn)單介紹SpringBoot框架
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著SpringBoot框架展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
springboot將mybatis升級(jí)為mybatis-plus的實(shí)現(xiàn)
之前項(xiàng)目工程用的是mybatis,現(xiàn)在需要將其替換為mybatis-plus,本文主要介紹了springboot將mybatis升級(jí)為mybatis-plus的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
idea解決程序包不存在報(bào)錯(cuò)的八種解決方法
這篇文章主要介紹了idea解決程序包不存在報(bào)錯(cuò)的八種解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-02-02
SpringBoot超詳細(xì)講解多數(shù)據(jù)源集成
今天分享下SpringBoot多數(shù)據(jù)源集成,我怕麻煩,這里我覺(jué)得我的集成也應(yīng)該是最簡(jiǎn)單的,清晰明了2022-05-05
Spring集成Struts與Hibernate入門(mén)詳解
這篇文章主要給大家介紹了關(guān)于Spring集成Struts與Hibernate的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-03-03
Spring Framework 5.0 入門(mén)教程
Spring Framework提供了一個(gè)簡(jiǎn)易的開(kāi)發(fā)方式,這種開(kāi)發(fā)方式,將避免那些可能致使底層代碼變得繁雜混亂的大量的屬性文件和幫助類(lèi)。這篇文章主要介紹了Spring Framework 5.0 入門(mén)教程,感興趣的小伙伴們可以參考一下2018-05-05

