原型模式--克隆怪物大军

引子

小帅就职于一家游戏公司,参与开发一款RPG游戏,他负责设计游戏里的怪物。有些大场面需要成百上千的怪物,如果用new的方法创建每一个怪物,需要初始化的参数很多,会比较耗时间,而且也比较麻烦。

小帅决定用原型模式快速地克隆怪物,让怪物大军迅速集结。

原型模式

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

在这里插入图片描述

原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。

对应我们的代码,类图如下:
在这里插入图片描述

怪物类:

/**
 * 怪物类
 */
public class Monster implements Cloneable{
    
    

    /**
     * 名称
     */
    String name;

    /**
     * 攻击力
     */
    int attackPower;

    /**
     * 生命值
     */
    int hp;

    public Monster(String name, int attackPower, int hp) {
    
    
        this.name = name;
        this.attackPower = attackPower;
        this.hp = hp;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    @Override
    public String toString() {
    
    
        return "怪物名称:" + name + ",攻击力:" + attackPower + ",生命值:" + hp;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getAttackPower() {
    
    
        return attackPower;
    }

    public void setAttackPower(int attackPower) {
    
    
        this.attackPower = attackPower;
    }

    public int getHp() {
    
    
        return hp;
    }

    public void setHp(int hp) {
    
    
        this.hp = hp;
    }

}

客户端类:

public class Client {
    
    

    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        List<Monster> monsterList = new ArrayList<Monster>();
        Monster monster = new Monster("飞龙", 200, 100);

        for(int i = 0; i < 10; i++) {
    
    
            monsterList.add((Monster)monster.clone());
        }

        monsterList.stream().forEach(f -> System.out.println(f));
    }
}

输出:

怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100

注意这里的Monster是要实现Cloneable接口才能使用clone()方法,如果把Cloneable接口去掉是会报错的:

Exception in thread "main" java.lang.CloneNotSupportedException: prototype.monster.normal.Monster
	at java.lang.Object.clone(Native Method)
	at prototype.monster.normal.Monster.clone(Monster.java:31)
	at prototype.monster.normal.Client.main(Client.java:13)

Object类中clone()方法中已经说明了:
在这里插入图片描述

浅拷贝和深拷贝

如果每个怪物都有自己的宠物,宠物有自己的名称和技能,我们再来看看下面的例子。

浅拷贝

怪物类:

/**
 * 怪物类
 */
public class Monster implements Cloneable{
    
    

    /**
     * 名称
     */
    String name;

    /**
     * 攻击力
     */
    int attackPower;

    /**
     * 生命值
     */
    int hp;

    /**
     * 宠物
     */
    Pet pet;

    public Monster(String name, int attackPower, int hp, Pet pet) {
    
    
        this.name = name;
        this.attackPower = attackPower;
        this.hp = hp;
        this.pet = pet;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
    
    
        return super.clone();
    }

    @Override
    public String toString() {
    
    
        return "怪物名称:" + name + ",攻击力:" + attackPower + ",生命值:" + hp + ", 宠物名称:" + pet.name + ",技能:" + pet.skill;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getAttackPower() {
    
    
        return attackPower;
    }

    public void setAttackPower(int attackPower) {
    
    
        this.attackPower = attackPower;
    }

    public int getHp() {
    
    
        return hp;
    }

    public void setHp(int hp) {
    
    
        this.hp = hp;
    }

    public Pet getPet() {
    
    
        return pet;
    }

    public void setPet(Pet pet) {
    
    
        this.pet = pet;
    }
}

宠物类:

/**
 * 宠物类
 */
public class Pet {
    
    

    /**
     * 名称
     */
    String name;

    /**
     * 技能
     */
    String skill;

    public Pet(String name, String skill) {
    
    
        this.name = name;
        this.skill = skill;
    }

    @Override
    public String toString() {
    
    
        return "宠物名称:" + name + ",技能:" + skill;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getSkill() {
    
    
        return skill;
    }

    public void setSkill(String skill) {
    
    
        this.skill = skill;
    }
}

客户端类:

public class Client {
    
    
    public static void main(String[] args) throws CloneNotSupportedException {
    
    
        // 宠物
        Pet pet = new Pet("小石头人", "飞石");
        // 怪兽
        Monster monster = new Monster("山岭巨人", 300, 500, pet);
        // 怪兽副本
        Monster monsterClone = (Monster)monster.clone();
        System.out.println("monster :" + monster);
        System.out.println("monsterClone :" + monsterClone);
        System.out.println("----------------------------------------------------------------------------------------------");
        // 只是修改怪兽副本的宠物属性
        monsterClone.pet.setName("飞鹰");
        monsterClone.pet.setSkill("俯冲");
        System.out.println("monster :" + monster);
        System.out.println("monsterClone :" + monsterClone);
    }
}

输出:

monster :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:小石头人,技能:飞石
monsterClone :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:小石头人,技能:飞石
----------------------------------------------------------------------------------------------
monster :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:飞鹰,技能:俯冲
monsterClone :怪物名称:山岭巨人,攻击力:300,生命值:500, 宠物名称:飞鹰,技能:俯冲

从上面的例子可以看到,复制出来的怪物对象monsterClone修改了自己的宠物pet的属性,同时也修改了原型怪物的宠物属性。

这是因为在 Java 语言中,Object 类的 clone() 方法执行的就是上面的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象(pet)的内存地址,不会递归地拷贝引用对象本身。

所以,monster和monsterClone对象引用的是同一个pet对象

深拷贝

下面我们来看看深拷贝的例子:

宠物类:
在这里插入图片描述
客户端类:
在这里插入图片描述
输出:
在这里插入图片描述
可以看到,复制的怪物只是修改了自己的宠物,原型怪物的宠物没有改变。

深拷贝就是把对象里的对象都一一拷贝了,每个对象里所有的数据都有独立的副本。

下面两张图描述了浅拷贝和深拷贝的区别:
在这里插入图片描述
在这里插入图片描述
还有一种实现深拷贝的方法就是序列化和反序列化:

public Object deepCopy(Object object) {
    
     
	ByteArrayOutputStream bo = new ByteArrayOutputStream(); 
	ObjectOutputStream oo = new ObjectOutputStream(bo); 
	oo.writeObject(object); 
	
	ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); 
	ObjectInputStream oi = new ObjectInputStream(bi); 
	
	return oi.readObject();
}

总结

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(拷贝、克隆)的方式,来创建新对象,以达到节省创建时间的目的。

原型模式是在内存二进制层面拷贝对象,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更加高效。

这就是原型模式,是不是很简单?

最后,我们看看原型模式的优点和缺点:

优点

  • 可以克隆对象, 而无需与它们所属的具体类相耦合。
  • 可以克隆预生成原型, 避免反复运行初始化代码。
  • 可以更方便地生成复杂对象。
  • 可以用继承以外的方式来处理复杂对象的不同配置。

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦。

代码链接

猜你喜欢

转载自blog.csdn.net/zhanyd/article/details/117190303