原型模式:快速複製已有實例建立新的實例

原型模式(Prototype Pattern)定義:用一個已經建立的實例做爲原型,經過複製該原型對象來建立一個和原型相同或類似的新對象java

場景

在《英雄聯盟》這款遊戲裏,有不少小兵。咱們定義小兵類:markdown

// Minion.java
public class Minion{
    /**
     * 小兵類型:近戰小兵MeleeMinion、遠程小兵CasterMinion、攻城小兵SiegeMinion、超級兵SuperMinion
     */
    private String type;

    /**
     * 顏色
     */
    private String color;

    /**
     * 血量
     */
    private int hp;

    /**
     * 武器
     */
    private Weapon weapon;

    public Minion(String type, String color, int hp) {
        System.out.println("開始構造小兵");
        this.type = type;
        this.color = color;
        this.hp = hp;
    }
}
複製代碼

咱們建立一個小兵,只須要這樣Minion minion = new Minion("Melee", "Blue", 200); app

在每一波兵中,都有五個這樣小兵,咱們就須要new 五個對象。若是這個構造方法是一個很是複雜或耗性能的操做,那咱們建立五個對象過程當中,就消耗大量系統資源。ide

在這種場景中,就很是適合原型模式:用一個已經建立的小兵做爲原型,經過複製該小兵來建立和這個小兵相同或類似的其餘小兵。函數

實現方式

原型模式的核心就是經過複製現有的實例建立新的實例。在Java的Object類中,clone()方法爲咱們提供了複製對象的功能。性能

protected native Object clone() throws CloneNotSupportedException;
複製代碼

具體的實現方式,只須要在類中實現Cloneable接口(若類沒有實現Cloneable接口,調用clone方法將拋出CloneNotSupportedException異常)。ui

// 小兵類,實現Cloneable接口
public class Minion implements Cloneable{
    /**
     * 小兵類型:近戰小兵MeleeMinion、遠程小兵CasterMinion、攻城小兵SiegeMinion、超級兵SuperMinion
     */
    private String type;

    /**
     * 顏色
     */
    private String color;

    /**
     * 血量
     */
    private int hp;

    public Minion(String type, String color, int hp) {
        System.out.println("開始構造小兵");
        this.type = type;
        this.color = color;
        this.hp = hp;
    }

    @Override
    public Minion clone() {
        try {
            // 調用Object類clone方法便可
            Minion copy = (Minion) super.clone();
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void display() {
        System.out.println("我是" + type + "小兵" + ";顏色:" + color + ";血量:" + hp);
    }

    public Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public static void main(String[] args) throws Exception{
        Minion minion = new Minion("Melee", "Blue", 200);
        Minion minionCopy =  minion.clone();
        System.out.println("minion == minionCopy:" + (minion == minionCopy));
        minionCopy.display();
    }
}
// 輸出結果:
開始構造小兵
minion == minionCopy:false
我是Melee小兵;顏色:Blue;血量:200
複製代碼

從輸出結果咱們能夠看出:this

  • 調用clone方法建立對象跟原型對象不是同一個對象
  • clone方法建立對象不調用類的構造方法(從輸出結果中只打印一次「開始構造小兵」能夠看出)

淺拷貝&深拷貝

拷貝對象(clone),能夠分爲兩種拷貝方式:lua

淺拷貝:拷貝對象的引用,而不是copy對象自己url

深拷貝:拷貝對象的自己,即會建立一個新的對象

java提供的clone方法,實現的是淺拷貝。咱們能夠實際驗證一下:

仍是剛纔的Minion類,如今給小兵加一個武器Weapon

// 武器類
public class Weapon implements Cloneable{
    /**
     * 武器類型
     */
    private String type;

    /**
     * 武器是否損壞
     */
    private boolean isDamage;

    public Weapon(String type) {
        this.type = type;
        isDamage = false;
    }

    /**
     * 武器損壞
     */
    public void destroy() {
        this.isDamage = true;
    }

    /**
     * 武器是否損壞
     * @return
     */
    public boolean isDamage() {
        return isDamage;
    }
}

// 小兵類
public class Minion implements Cloneable{
    
    /**
     * 武器
     */
    private Weapon weapon;


    public Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }

    public static void main(String[] args) throws Exception{
        Minion minion = new Minion("Melee", "Blue", 200);
        Weapon weapon = new Weapon("刀");
        minion.setWeapon(weapon);
        Minion minionCopy =  minion.clone();

        System.out.println("複製小兵1武器是否損壞:" + minionCopy.getWeapon().isDamage());
        // 原始小兵武器損壞
        minion.getWeapon().destroy();
        System.out.println("複製小兵1武器是否損壞:" + minionCopy.getWeapon().isDamage());
    }
}

// 輸出結果
開始構造小兵
複製小兵1武器是否損壞:false
複製小兵1武器是否損壞:true
複製代碼

在main方法中代碼中,咱們只讓原始小兵武器損壞,但從輸出結果來看,咱們複製小兵的武器也壞了。這就印證了咱們上面的結論:java提供的clone方法,實現的是淺拷貝。

如何實現深拷貝?

其實很簡單,咱們能夠手動建立須要深拷貝的對象

public Minion clone() {
        try {
            Minion copy = (Minion) super.clone();
            // 手動建立對象
            Weapon weapon = new Weapon(this.getWeapon().getType());
            copy.setWeapon(weapon);
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
複製代碼

這種手動建立的方式違背了咱們克隆對象的初衷,咱們能夠改爲:對須要拷貝的成員對象再次克隆

// 武器對象實現Cloneable接口
public class Weapon implements Cloneable{
    @Override
    public Weapon clone() {
        try {
            return (Weapon) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

// 小兵類的clone方法
    @Override
    public Minion clone() {
        try {
            Minion copy = (Minion) super.clone();
            // 克隆對象
            Weapon weapon = this.weapon.clone();
            copy.setWeapon(weapon);
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
複製代碼

這種克隆對象方式,有時實現起來會特別複雜,好比有大量的非基本數據類型的成員對象;成員對象的引用嵌套很是深,須要對每層都實現clone方法。

更通用的實現深拷貝的方式:經過序列化和反序列化的方式實現對象的拷貝

Java序列化是指將Java對象轉爲字節序列的過程,反序列化爲把字節序列恢復爲Java對象的過程。

具體代碼:

@Override
    public Minion clone() {
        // 序列化、反序列化方式
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return (Minion) objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
複製代碼

!!! 將要序列化的對象及其成員變量必定要實現Serializable接口,不然序列化時將拋出ava.io.NotSerializableException異常。

原型模式的優勢

  • 隱藏建立新對象的複雜性
  • 能夠建立未知類型的對象(直接調用clone方法,不關心對象類型)
  • 能夠跳過構造函數,快速建立對象(有時候也會成爲其缺點,跳過構造函數執行的邏輯)
相關文章
相關標籤/搜索