原型模式:快速复制已有实例建立新的实例

原型模式(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方法,不关心对象类型)
  • 能够跳过构造函数,快速建立对象(有时候也会成为其缺点,跳过构造函数执行的逻辑)
相关文章
相关标签/搜索