前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >对象复制的魔法——探索原型模式的魅力

对象复制的魔法——探索原型模式的魅力

原创
作者头像
码匠er
发布2024-03-01 20:45:11
620
发布2024-03-01 20:45:11
举报
文章被收录于专栏:设计模式设计模式

原型模式很简单,通过原型模式,你可以克隆出多个一模一样的对象

1. 定义

原型模式是使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。

2. 结构

  • Prototype抽象原型类声明了克隆方法的接口,是具体原型类的父类
  • ConcretePrototype具体原型类实现了抽象原型类中声明的克隆方法,该方法返回一个自己的克隆对象
  • Client:客户类让一个原型类克隆自身,从而创建新的对象

3. 设计原理

原型模式的设计原理是客户端将调用原型对象的克隆方法自己实现创建过程。原型对象的核心就是对象克隆

4. 案例分析

假如我们需要开发一款游戏,我们需要生成许多怪物,我们可以使用原型对象对怪物进行创建和管理。

抽象原型对象:

代码语言:javascript
复制
 public abstract class Monster implements Cloneable {
     private String name;
     private int HP; // 生命值
 
     public Monster(String name, int HP) {
         this.name = name;
         this.HP = HP;
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public int getHP() {
         return HP;
     }
 
     public void setHP(int HP) {
         this.HP = HP;
     }
 

具体原型对象:

代码语言:javascript
复制
 public class Goblin extends Monster{
 
     public Goblin(String name, int HP) {
         super(name, HP);
     }
 
    @Override
     protected Goblin clone() {
         Object obj = null;
         try {
             obj = super.clone();
             return (Goblin) obj;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
         }
         return null;
     }
 }

客户端:

代码语言:javascript
复制
 public class Client {
     public static void main(String[] args) {
         // 先创建一个小妖精
         Goblin goblin = new Goblin("小妖精", 100);
         System.out.println(goblin.getName());
         System.out.println(goblin.getHP());
         // 通过原型创建一个新的妖精
         Goblin newGobin = goblin.clone();
         System.out.println(newGobin.getName());
         System.out.println(newGobin.getHP());
         // 自己给妖精设置属性
         newGobin.setName("大妖精");
         newGobin.setHP(500);
         System.out.println("小妖精的名字:" + goblin.getName() + "; 小妖精的生命值:" + goblin.getHP());
         System.out.println("新妖精的名字:" + newGobin.getName() + "; 新妖精的生命值:" + newGobin.getHP());
     }
 }

创建新的怪物对象时,不需要重新使用new关键字创建,直接克隆。

我们可以看到我们很快就创建了一个独立的对象,而且和之前的原型对象是独立的。

下面需要进行优化,每个怪物都有自身独有的技能,我们改造一下代码。

代码语言:javascript
复制
 public class Skilledness {
     private String name;
     private int damage;
 
     public Skilledness(String name, int damage) {
         this.name = name;
         this.damage = damage;
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public int getDamage() {
         return damage;
     }
 
     public void setDamage(int damage) {
         this.damage = damage;
     }
 
     @Override
     public String toString() {
         return "Skilledness{" +
                 "name='" + name + '\'' +
                 ", damage=" + damage +
                 '}';
     }
 }
代码语言:javascript
复制
 public abstract class Monster implements Cloneable {
     private String name;
     private int HP; // 生命值
     // 新增技能属性
     private Skilledness skilledness;
 
     public Monster(String name, int HP) {
         this.name = name;
         this.HP = HP;
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public int getHP() {
         return HP;
     }
 
     public void setHP(int HP) {
         this.HP = HP;
     }
 
     public Skilledness getSkilledness() {
         return skilledness;
     }
 
     public void setSkilledness(Skilledness skilledness) {
         this.skilledness = skilledness;
     }
 }
代码语言:javascript
复制
 public class Orge extends Monster{
 
     public Orge(String name, int HP) {
         super(name, HP);
     }
 
     @Override
     protected Orge clone() {
         Object obj = null;
         try {
             obj = super.clone();
             return (Orge) obj;
         } catch (CloneNotSupportedException e) {
             e.printStackTrace();
         }
         return null;
     }
 }

客户端:

代码语言:javascript
复制
 public class Client {
     public static void main(String[] args) {
         // 先创建一个恶魔
         Orge orge = new Orge("大恶魔", 1000);
         // 给恶魔设置一个技能
         Skilledness skilledness = new Skilledness("鬼斩", 200);
         orge.setSkilledness(skilledness);
         // 再创建一个恶魔
         Orge orge1 = orge.clone();
         System.out.println("新恶魔名称:" + orge1.getName());
         System.out.println("新恶魔生命值:" + orge1.getHP());
         System.out.println("新恶魔技能:" + orge1.getSkilledness());
         // 修改技能
         orge1.getSkilledness().setDamage(300);
         // 查看技能伤害值
         // 为什么复制的技能伤害值改变,原来的技能伤害值也改变了?
         System.out.println("原恶魔伤害:" + orge.getSkilledness().getDamage());
         System.out.println("新恶魔伤害:" + orge1.getSkilledness().getDamage());
     }
 }

5. 深拷贝和浅拷贝

上面的代码中,我们可以看出,技能在复制时,新的技能改变,原来的技能值也改变了,这并不是我们想要的结果,这个是为什么呢?我们应该怎么实现对技能真正的复制呢?

回答上面的代码之前,需要先了解两种不同的克隆方法,分别是深拷贝浅拷贝

  • 浅拷贝:创建一个新的对象,然后将原始对象的非静态字段的值赋值到新的对象,如果包含引用对象,则将引用对象的地址复制一份给克隆的对象,也就是说新的对象和原对象的成员变量指向相同的内存地址。

上面代码中都属于浅拷贝的实现,所以当新的技能值改变之后,原来的技能值也会发生改变。

  • 深拷贝:创建一个新的对象,并且递归的复制原始对象及所有引用类型的成员变量,使得新的对象和原对象完全独立。深拷贝建创建的对象和相关的对象都是新的,不是共享同一引用。

在Java语言中,深拷贝的实现可以考虑使用序列化等方式。通过将对象序列化成字节流,然后再将字节流反序列化为新的对象。下面是实现对技能的深拷贝实现。

因为需要实现序列化,所以怪物类和技能类都需要实现Serializable接口,这里就不单独展示了。

代码语言:javascript
复制
 public class Orge extends Monster {
 
     public Orge(String name, int HP) {
         super(name, HP);
     }
 
     protected Orge deepClone() throws IOException, ClassNotFoundException {
         // 序列化为字节流
         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
         objectOutputStream.writeObject(this);
         // 反序列化
         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
         ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return (Orge) objectInputStream.readObject();
     }
 }
代码语言:javascript
复制
public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 先创建一个恶魔
        Orge orge = new Orge("大恶魔", 1000);
        // 给恶魔设置一个技能
        Skilledness skilledness = new Skilledness("鬼斩", 200);
        orge.setSkilledness(skilledness);
        // 再创建一个恶魔
        Orge orge1 = orge.deepClone();
        System.out.println("新恶魔名称:" + orge1.getName());
        System.out.println("新恶魔生命值:" + orge1.getHP());
        System.out.println("新恶魔技能:" + orge1.getSkilledness());
        // 修改技能
        orge1.getSkilledness().setDamage(300);
        // 查看技能伤害值
        // 新恶魔伤害值修改了,但是原恶魔伤害值没有修改,深拷贝成功
        System.out.println("原恶魔伤害:" + orge.getSkilledness().getDamage());
        System.out.println("新恶魔伤害:" + orge1.getSkilledness().getDamage());
    }
}

6.UML图

7. 原型管理器

原型管理器是将多个原型对象存储在一个集合中供客户端使用,它是专门负责创建对象的工厂。

代码语言:javascript
复制
public class MonsterManager {
    private static Map<String, Monster> monsterMap = new HashMap<>();

    static {
        monsterMap.put("Goblin", new Goblin("Small Goblin", 100));
        monsterMap.put("Orge", new Goblin("Big Goblin", 1000));
    }

    public static Monster getMonster(String type) {
        Monster monster = monsterMap.get(type);
        return monster != null ? monster.clone() : null;
    }

    public static void addMonster(String type, Monster monster) {
        monsterMap.put(type, monster);
    }
}

在上述代码中引入了原型管理器,创建怪物时只需要使用管理器来进行创建即可,管理器中也是使用克隆的方式来创建的。

8. 优缺点

8.1 优点

  • 性能提高:克隆对象比直接创建对象的性能更好,通过复制现有对象,避免初始化对象的步骤;
  • 扩展性好:由于在原型模式中引入了抽象原型类,可以针对抽象进行编程,可以实现对具体原型类的扩展;(符合依赖导致)
  • 状态保存:可以使用深拷贝的方法保存对象的状态,使用原型模式将对象复制一份并将其保存,可以方便后续恢复到某一历史状态;

8.2 缺点

  • 克隆实现困难:每一个类都需要重写克隆方法,当修改克隆逻辑时,需要修改已有代码;(违反开闭原则)
  • 负责对象处理困难:如果对象包含循环引用或者其他负责结构时,需要考虑拷贝方式,需要注意考虑深拷贝和浅拷贝的问题;

9. 使用场景

  • 对象创建成本高:如果创建一个对象需要占用太多的资源,可以使用原型模式,避免了初始化对象所需的大部分步骤,提高性能;
  • 类实例之间区别小:如果一个类的实例之间区别较小,通过复制已有实例的数据创建新的实例,而不是通过构造函数初始化;
  • 大量相似对象的创建:在需要创建大量相似对象的情况下,原型模式可以通过复制原型对象来生成大量对象,避免了重复的初始化过程;

我会持续更新关于技术的文章❤️🤎💚🧡💛

欢迎大家点赞👍 收藏 ⭐ 关注 💡三连支持一下~~~

查看文章过程中有问题或者有需要修改的地方,欢迎私聊我哦 🗨🗨🗨

不管世界变成什么样,我们都要加强自己自身能力~✊✊✊

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 定义
  • 2. 结构
  • 3. 设计原理
  • 4. 案例分析
  • 5. 深拷贝和浅拷贝
  • 6.UML图
  • 7. 原型管理器
  • 8. 优缺点
    • 8.1 优点
      • 8.2 缺点
      • 9. 使用场景
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档