天天看點

java設計模式之原型模式|淺複制和深複制的了解

目錄

​​一、前言​​

​​優點及适用場景​​

​​原型模式的注意事項​​

​​淺複制和深複制​​

​​二、淺複制demo示範​​

​​三、深複制demo示範​​

​​ 四、項目位址​​

一、前言

原型模式是一種比較簡單的模式,也非常容易了解,實作一個接口,重寫一個方法即完成了原型模式。在實際應用中,原型模式很少單獨出現。經常與其他模式混用,他的原型類Prototype也常用抽象類來替代。

該模式的思想就是将一個對象作為原型,對其進行複制、克隆,産生一個和原對象類似的新對象。在Java中,複制對象是通過clone()實作的,先建立一個原型類,通過實作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名字可以任意取,是因為要你主動去調用的,是以你名字取成什麼,你調用的時候就調用該名字就可以了

優點及适用場景

       使用原型模式建立對象比直接new一個對象在性能上要好的多,因為上面我也提到過,Object類的clone()是native的,它直接操作記憶體中的二進制流,特别是複制大對象時,性能的差别非常明顯。

       使用原型模式的另一個好處是簡化對象的建立,使得建立對象就像我們在編輯文檔時的複制粘貼一樣簡單。

       因為以上優點,是以在需要重複地建立相似對象時可以考慮使用原型模式。比如需要在一個循環體内建立對象,假如對象建立過程比較複雜或者循環次數很多的話,使用原型模式不但可以簡化建立過程,而且可以使系統的整體性能提高很多。

原型模式的注意事項

使用原型模式複制對象不會調用類的構造方法。因為對象的複制是通過調用Object類的clone()來完成的,它直接在記憶體中複制資料,是以不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連通路權限都對原型模式無效。

說到這裡,就得順便提一下單例模式,在單例模式中,隻要将構造方法的通路權限設定為private型,就可以實作單例。但是clone方法直接無視構造方法的權限,是以,單例模式與原型模式是沖突的,在使用時要特别注意。

淺複制和深複制

另外還得知道兩個特别重要的概念 : 淺複制   深複制

    淺複制:将一個對象複制後,基本資料類型的變量都會重新建立,而數組、容器對象、引用對象等都不會拷貝,指向的還是原對象所指向的位址。淺拷貝實作 Cloneable,重寫clone方法

    深複制:将一個對象複制後,不論是基本資料類型還有引用類型,都是重新建立的。簡單來說,就是深複制進行了完全徹底的複制,而淺複制不徹底。深拷貝是通過實作 Serializable 讀取二進制流

二、淺複制demo示範

首先我們建立一個抽象原型類 Animal.class,實作了Cloneable接口,并且重寫了clone方法

package cn.zygxsq.design.module.prototypePattern;

/**
* Created by yjl on 2021/4/30.
* 動物類
*
*/
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;
    }
}
      

再建立兩個實作類

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("我的叫聲是:喵喵喵");
    }
}
      

然後建立一個資料緩存類,使用者存儲從資料庫中擷取到的大對象資料或者曾經使用過的大對象資料

以後下一次想要再次對這個對象資料操作的時候,直接從緩存裡擷取并且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.
* 測試主類 淺複制
*
*/
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();
        }


    }
}
      

運作結果: 

java設計模式之原型模式|淺複制和深複制的了解

小夥伴們看的時候,好像沒什麼問題,确實沒什麼問題,但是細細一看,還是有一定的問題的

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是什麼樣的關系呢

java設計模式之原型模式|淺複制和深複制的了解

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

java設計模式之原型模式|淺複制和深複制的了解

那怎麼樣才能不讓a1.name 和a2.name不相同呢,也就是完完全全的複制,這個就得用到深複制了

深複制其實用到的就是流複制

可以在clone()的方法定義一個深複制的方法,比如deepClone()

三、深複制demo示範

記住,深複制的時候,方法一定得實作可序列化,Serializable

package cn.zygxsq.design.module.prototypePattern;

import java.io.*;

/**
* Created by yjl on 2021/4/30.
* 動物類
* 
*/
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();
            }

        }

    }
}
      
java設計模式之原型模式|淺複制和深複制的了解

測試一下結果

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
* Created by yjl on 2021/4/30.
* 測試主類 深複制
*
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設計模式之原型模式|淺複制和深複制的了解

這就是深複制和淺複制以及原型模式的使用

 四、項目位址

github位址:

繼續閱讀