Java設計模式之原型模式詳解
一、前言
原型模式是一種比較簡單的模式,也非常容易理解,實現一個接口,重寫一個方法即完成了原型模式。在實際應用中,原型模式很少單獨出現。經常與其他模式混用,他的原型類Prototype也常用抽象類來替代。
該模式的思想就是將一個對象作為原型,對其進行復制、克隆,產生一個和原對象類似的新對象。在Java中,復制對象是通過clone()實現的,先創(chuàng)建一個原型類,通過實現Cloneable 接口
public class Prototype implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
}
只需要實現Cloneable接口,覆寫clone方法,此處clone方法可以改成任意的名稱,因為Cloneable接口是個空接口,你可以任意定義實現類的方法名,如cloneA或者cloneB,因為此處的重點是super.clone()這句話,super.clone()調用的是Object的clone()方法,而在Object類中,clone()是native的,說明這個方法實現并不是使用java語言,是底層C實現阿達
至于cloneA或者cloneB名字可以任意取,是因為要你主動去調用的,所以你名字取成什么,你調用的時候就調用該名字就可以了
二、優(yōu)點及適用場景
使用原型模式創(chuàng)建對象比直接new一個對象在性能上要好的多,因為上面我也提到過,Object類的clone()是native的,它直接操作內存中的二進制流,特別是復制大對象時,性能的差別非常明顯。
使用原型模式的另一個好處是簡化對象的創(chuàng)建,使得創(chuàng)建對象就像我們在編輯文檔時的復制粘貼一樣簡單。
因為以上優(yōu)點,所以在需要重復地創(chuàng)建相似對象時可以考慮使用原型模式。比如需要在一個循環(huán)體內創(chuàng)建對象,假如對象創(chuàng)建過程比較復雜或者循環(huán)次數很多的話,使用原型模式不但可以簡化創(chuàng)建過程,而且可以使系統(tǒng)的整體性能提高很多。
三、原型模式的注意事項
使用原型模式復制對象不會調用類的構造方法。因為對象的復制是通過調用Object類的clone()來完成的,它直接在內存中復制數據,因此不會調用到類的構造方法。不但構造方法中的代碼不會執(zhí)行,甚至連訪問權限都對原型模式無效。
說到這里,就得順便提一下單例模式,在單例模式中,只要將構造方法的訪問權限設置為private型,就可以實現單例。但是clone方法直接無視構造方法的權限,所以,單例模式與原型模式是沖突的,在使用時要特別注意。
四、淺復制和深復制
另外還得知道兩個特別重要的概念 : 淺復制 深復制
淺復制:將一個對象復制后,基本數據類型的變量都會重新創(chuàng)建,而數組、容器對象、引用對象等都不會拷貝,指向的還是原對象所指向的地址。淺拷貝實現 Cloneable,重寫clone方法
深復制:將一個對象復制后,不論是基本數據類型還有引用類型,都是重新創(chuàng)建的。簡單來說,就是深復制進行了完全徹底的復制,而淺復制不徹底。深拷貝是通過實現 Serializable 讀取二進制流
五、淺復制demo演示
首先我們創(chuàng)建一個抽象原型類 Animal.class,實現了Cloneable接口,并且重寫了clone方法
package cn.zygxsq.design.module.prototypePattern;
/**
* Created by yjl on 2021/4/30.
* 動物類
* 原型模式:博文介紹鏈接:https://blog.csdn.net/qq_27471405/article/details/116309878
*/
public abstract class Animal implements Cloneable{
private String id;
public String name;
abstract void shout();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 淺復制
*/
public Object clone() throws CloneNotSupportedException {
Animal clone = (Animal) super.clone();
return clone;
}
}
再創(chuàng)建兩個實現類
Dog.class 和Cat.class
package cn.zygxsq.design.module.prototypePattern;
/**
* Created by yjl on 2021/4/30.
*/
public class Dog extends Animal {
public Dog(){
name = "狗狗";
}
@Override
public void shout() {
System.out.println("我的叫聲是:汪汪汪");
}
}
package cn.zygxsq.design.module.prototypePattern;
/**
* Created by yjl on 2021/4/30.
*/
public class Cat extends Animal {
public Cat(){
name = "貓貓";
}
@Override
public void shout() {
System.out.println("我的叫聲是:喵喵喵");
}
}
然后創(chuàng)建一個數據緩存類,用戶存儲從數據庫中獲取到的大對象數據或者曾經使用過的大對象數據
以后下一次想要再次對這個對象數據操作的時候,直接從緩存里獲取并且clone一個
package cn.zygxsq.design.module.prototypePattern;
import com.google.common.collect.Maps;
import org.springframework.beans.factory.InitializingBean;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentMap;
/**
* Created by yjl on 2021/4/30.
* 緩存類 用于加載一些數據庫的緩存的數據
*/
public class DataCache /*implements InitializingBean*/{
//正常的情況是 實現 InitializingBean ,用于web服務啟動的時候加載數據
// 這里測試由于不是web服務,所以就模擬加載數據
private static ConcurrentMap<String, Animal> animalCache = Maps.newConcurrentMap();
public static Animal getAnimal(String id) throws Exception{
Animal cache = animalCache.get(id);
return (Animal) cache.clone();
}
public static void init(){
Dog dog = new Dog();
String dogid = "111";
dog.setId(dogid);
animalCache.put(dogid,dog);
Dog dog2 = new Dog();
String dogid2 = "222";
dog2.setId(dogid2);
animalCache.put(dogid2,dog2);
Cat cat = new Cat();
String catid = "333";
cat.setId(catid);
animalCache.put(catid,cat);
}
}
最后咱們開始測試
先是從數據庫里加載緩存,然后要從緩存里獲取數據,并且的到的是一個個clone出來的對象
package cn.zygxsq.design.module.prototypePattern;
import com.alibaba.fastjson.JSON;
/**
* Created by yjl on 2021/4/30.
* 測試主類 淺復制
* 原型模式:博文介紹鏈接:https://blog.csdn.net/qq_27471405/article/details/116309878
*/
public class TestPrototype {
public static void main(String[] args) {
DataCache.init(); // 模擬加載數據到緩存中
try {
Animal animal = DataCache.getAnimal("111");
System.out.println(animal.getName()+"---"+JSON.toJSONString(animal));
Animal animal222 = DataCache.getAnimal("222");
System.out.println(animal222.getName()+"---"+JSON.toJSONString(animal222));
Animal animal333 = DataCache.getAnimal("333");
System.out.println(animal333.getName()+"---"+JSON.toJSONString(animal333));
animal.shout();
animal222.shout();
animal333.shout();
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結果:

小伙伴們看的時候,好像沒什么問題,確實沒什么問題,但是細細一看,還是有一定的問題的
package cn.zygxsq.design.module.prototypePattern;
import com.alibaba.fastjson.JSON;
/**
* Created by yjl on 2021/4/30.
* 淺復制遇到的問題
*/
public class TestCloneProblem {
public static void main(String[] args) {
//做完TestPrototype的main方法后,好像覺得淺復制沒有什么問題
//那么可以看一下下面的a1的name 和 克隆后的name指向的是同一個地址
DataCache.init(); // 模擬加載數據到緩存中
try {
Animal a1 = DataCache.getAnimal("111");
Animal a2 = (Animal)a1.clone();
System.out.println(a1==a2);
System.out.println(a1.name == a2.name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結果:
大家踩一下 a1的name 和 克隆后的name是什么樣的關系呢

大家可以看到,a1的name和a2的name是一樣的,由于他們的類型是String,所以他們指向的是同一個地址,名稱為“狗狗”的引用地址,大概的樣子可以看下圖

那怎么樣才能不讓a1.name 和a2.name不相同呢,也就是完完全全的復制,這個就得用到深復制了
深復制其實用到的就是流復制
可以在clone()的方法定義一個深復制的方法,比如deepClone()
六、深復制demo演示
記住,深復制的時候,方法一定得實現可序列化,Serializable
package cn.zygxsq.design.module.prototypePattern;
import java.io.*;
/**
* Created by yjl on 2021/4/30.
* 動物類
* 原型模式:博文介紹鏈接:https://blog.csdn.net/qq_27471405/article/details/116309878
*/
public abstract class Animal implements Cloneable, Serializable{
private String id;
public String name;
abstract void shout();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 淺復制
*/
public Object clone() throws CloneNotSupportedException {
Animal clone = (Animal) super.clone();
return clone;
}
/**
* 深復制
*/
public Object deepClone() {
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
// 序列化
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);/*將當前對象以對象流的方式輸出*/
//反序列化
byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
objectInputStream = new ObjectInputStream(byteArrayInputStream);
Animal deepProtoType = (Animal) objectInputStream.readObject();
return deepProtoType;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
byteArrayOutputStream.close();
objectOutputStream.close();
byteArrayInputStream.close();
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

測試一下結果
package cn.zygxsq.design.module.prototypePattern;
import com.alibaba.fastjson.JSON;
/**
* Created by yjl on 2021/4/30.
* 測試主類 深復制
* 原型模式:博文介紹鏈接:https://blog.csdn.net/qq_27471405/article/details/116309878
*/
public class TestPrototypeDeepClone {
public static void main(String[] args) {
DataCache.init(); // 模擬加載數據到緩存中
try {
Animal a1 = DataCache.getAnimal("111");
Animal a2 = (Animal)a1.deepClone();
System.out.println(a1==a2);
System.out.println(a1.name == a2.name);
System.out.println(a1.name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行結果:

這就是深復制和淺復制以及原型模式的使用
到此這篇關于Java設計模式之原型模式詳解的文章就介紹到這了,更多相關Java原型模式內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot整合RabbitMQ的5種模式的注解綁定詳解
這篇文章主要介紹了SpringBoot整合RabbitMQ的5種模式的注解綁定詳解,RabbitMQ 是一個消息中間件,它接收消息并且轉發(fā),是"消費-生產者模型"的一個典型的代表,一端往消息隊列中不斷的寫入消息,而另一端則可以讀取或者訂閱隊列中的消息,需要的朋友可以參考下2024-01-01
Spring Boot 自定義 Shiro 過濾器無法使用 @Autowired問題及解決方法
這篇文章主要介紹了Spring Boot 自定義 Shiro 過濾器無法使用 @Autowired問題及解決方法 ,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-06-06
shiro與spring?security用自定義異常處理401錯誤
這篇文章主要介紹了shiro與spring?security用自定義異常處理401錯誤,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

