前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >原型模式--克隆怪物大军

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

作者头像
zhanyd
发布2022-05-16 14:01:29
2160
发布2022-05-16 14:01:29
举报
文章被收录于专栏:编程我也会

引子

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

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

原型模式

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

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

对应我们的代码,类图如下:

怪物类:

代码语言:javascript
复制
/**
 * 怪物类
 */
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;
    }

}

客户端类:

代码语言:javascript
复制
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));
    }
}

输出:

代码语言:javascript
复制
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100
怪物名称:飞龙,攻击力:200,生命值:100

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

代码语言:javascript
复制
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()方法中已经说明了:

浅拷贝和深拷贝

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

浅拷贝

怪物类:

代码语言:javascript
复制
/**
 * 怪物类
 */
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;
    }
}

宠物类:

代码语言:javascript
复制
/**
 * 宠物类
 */
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;
    }
}

客户端类:

代码语言:javascript
复制
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);
    }
}

输出:

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

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

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

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

深拷贝

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

宠物类:

客户端类:

输出:

可以看到,复制的怪物只是修改了自己的宠物,原型怪物的宠物没有改变。

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

下面两张图描述了浅拷贝和深拷贝的区别:

还有一种实现深拷贝的方法就是序列化和反序列化:

代码语言:javascript
复制
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一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更加高效。

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

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

优点

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

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程我也会 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引子
  • 原型模式
  • 浅拷贝和深拷贝
    • 浅拷贝
      • 深拷贝
      • 总结
        • 优点
          • 缺点
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档