設(shè)計(jì)模式-原型模式

原型模式

? 原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象

? 調(diào)用者不需要知道任何創(chuàng)建細(xì)節(jié),不調(diào)用構(gòu)造函數(shù)

? 其屬于一種創(chuàng)建型模式

通用類圖

image-20210103173546197

優(yōu)點(diǎn)

  • 性能好
    • 是在內(nèi)存二進(jìn)制流的拷貝,比直接new一個(gè)對(duì)象性能好,而且循環(huán)體內(nèi)產(chǎn)生大量對(duì)象時(shí),可以更好地提現(xiàn)優(yōu)點(diǎn)
  • 逃避構(gòu)造函數(shù)的約束
    • 直接在內(nèi)存中拷貝構(gòu)造函數(shù)是不會(huì)執(zhí)行的

適用場(chǎng)景

  • 類初始化消耗資源較多
  • new 產(chǎn)生的一個(gè)對(duì)象需要非常繁瑣的過(guò)程(數(shù)據(jù)準(zhǔn)備、訪問(wèn)權(quán)限等)
    • 省略了自己去get,set的過(guò)程
  • 構(gòu)造函數(shù)比較復(fù)雜時(shí)
  • 循環(huán)體中產(chǎn)生大量對(duì)象時(shí)

使用

  • 通過(guò)一個(gè)特定的方法來(lái)拷貝對(duì)應(yīng)的對(duì)象

自己提供接口并且實(shí)現(xiàn)

使用JDK的clone方法

淺克隆

測(cè)試

@Data
public class ConcretePrototype implements Cloneable {

  private int age;
  private String name;
  private List<String> hobbies;

  @Override
  public ConcretePrototype clone() {
    try {
      return (ConcretePrototype)super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
      return null;
    }
  }
}
public static void main(String[] args) {
  ConcretePrototype prototype = new ConcretePrototype();
  prototype.setAge(18);
  prototype.setName("zzy");
  List<String> hobbies = new ArrayList<String>();
  hobbies.add("music");
  hobbies.add("article");
  prototype.setHobbies(hobbies);

  //拷貝原型對(duì)象
  ConcretePrototype cloneType = prototype.clone();
  cloneType.getHobbies().add("program");


  System.out.println("原型對(duì)象:" + prototype);
  System.out.println("克隆對(duì)象:" + cloneType);
  System.out.println(prototype == cloneType);


  System.out.println("原型對(duì)象的愛(ài)好:" + prototype.getHobbies());
  System.out.println("克隆對(duì)象的愛(ài)好:" + cloneType.getHobbies());
  System.out.println(prototype.getHobbies() == cloneType.getHobbies());

}

原型對(duì)象:ConcretePrototype{age=18, name='zzy', hobbies=[music, article, program]}
克隆對(duì)象:ConcretePrototype{age=18, name='zzy', hobbies=[music, article, program]}
false
原型對(duì)象的愛(ài)好:[music, article, program]
克隆對(duì)象的愛(ài)好:[music, article, program]
true

通過(guò)結(jié)果可以看到通過(guò)clone拷貝出來(lái)對(duì)象的集合類型的內(nèi)存地址沒(méi)有改變

基本數(shù)據(jù)類型直接拷貝了,但是引用數(shù)據(jù)類型拷貝的是屬性的內(nèi)存地址,具體的元素并沒(méi)有拷貝

這種方式會(huì)給我們未來(lái)的使用帶來(lái)風(fēng)險(xiǎn)

深克隆

序列化和反序列化的方式

手寫流

這種方式對(duì)象一定要記得序列化

public ConcretePrototype deepClone(){
  try {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);

    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);

    return (ConcretePrototype)ois.readObject();
  }catch (Exception e){
    e.printStackTrace();
    return null;
  }
  • 缺點(diǎn)
    • 性能不好
    • 占用IO
    • 沒(méi)有通過(guò)構(gòu)造方法來(lái)生成對(duì)象
      • 會(huì)破壞單例模式

通過(guò)json序列化和反序列化

常用的工具

Spring 的BeanUtils

org.springframework.beans.BeanUtils#copyProperties

Apache的Beanutils

org.apache.commons.beanutils.BeanUtils

不推薦使用,性能差

對(duì)于對(duì)象拷貝加了很多的檢驗(yàn),包括類型的轉(zhuǎn)換,甚至還會(huì)檢驗(yàn)對(duì)象所屬的類的可訪問(wèn)性,非常復(fù)雜,這也造就了它的差勁的性能,

原型模式在源碼中的提現(xiàn)

ArrayList

public class ArrayList<E> extends AbstractList<E>
  implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
  public Object clone() {
    try {
      ArrayList<?> v = (ArrayList<?>) super.clone();
      v.elementData = Arrays.copyOf(elementData, size);
      v.modCount = 0;
      return v;
    } catch (CloneNotSupportedException e) {
      // this shouldn't happen, since we are Cloneable
      throw new InternalError(e);
    }
  }
}

HashMap

public class HashMap<K,V> extends AbstractMap<K,V>
  implements Map<K,V>, Cloneable, Serializable {
  public Object clone() {
    HashMap<K,V> result;
    try {
      result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
      // this shouldn't happen, since we are Cloneable
      throw new InternalError(e);
    }
    result.reinitialize();
    result.putMapEntries(this, false);
    return result;
  }
}

注意事項(xiàng)

構(gòu)造函數(shù)不會(huì)被執(zhí)行

? 對(duì)象拷貝時(shí)構(gòu)造函數(shù)確實(shí)沒(méi)有被執(zhí)行,這點(diǎn)從原理來(lái)講也是可以講得通的,Object類的clone方法的原理是從內(nèi)存中(具體地說(shuō)就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝,重新分配一個(gè)內(nèi)存塊,那構(gòu)造函數(shù)沒(méi)有被執(zhí)行也是非常正常的了。

深拷貝和淺拷貝

深拷貝和淺拷貝建議不要混合使用,特別是在涉及類的繼承 時(shí),父類有多個(gè)引用的情況就非常復(fù)雜,建議的方案是深拷貝和淺拷貝

final修飾的變量是不會(huì)被拷貝的

問(wèn)題

通過(guò)實(shí)現(xiàn)Cloneable接口怎么實(shí)現(xiàn)克隆原理是什么?有什么問(wèn)題?代碼中是如何驗(yàn)證是深克隆還是淺克隆的?

一般都是直接基于內(nèi)存二進(jìn)制流來(lái)進(jìn)行拷貝,不會(huì)經(jīng)過(guò)構(gòu)造函數(shù),性能能夠提升很多。

  • 注意
    • 是淺拷貝的
      • 引用數(shù)據(jù)類型是不會(huì)被拷貝的,拷貝的是內(nèi)存地址,不會(huì)創(chuàng)建一個(gè)新的
      • final修飾的變量是不會(huì)被拷貝的
  • 驗(yàn)證方式
    • 通過(guò)比較內(nèi)存地址來(lái)判斷

深克隆有哪些解決辦法?

  • 通過(guò)序列化和反序列化
  • 通過(guò)Json工具

如果我需要單例,怎么防止克隆破壞單例

  • 重寫readResolve方法

我的筆記倉(cāng)庫(kù)地址gitee 快來(lái)給我點(diǎn)個(gè)Star吧

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容