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

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

1. 定义

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

2. 结构

  • Prototype抽象原型类声明了克隆方法的接口,是具体原型类的父类

  • ConcretePrototype具体原型类实现了抽象原型类中声明的克隆方法,该方法返回一个自己的克隆对象

  • Client:客户类让一个原型类克隆自身,从而创建新的对象

3. 设计原理

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

4. 案例分析

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

抽象原型对象:

 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;
     }
 ​

具体原型对象:

 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;
     }
 }

客户端:

 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关键字创建,直接克隆。

7b59bc65dbea485e9621cee80981989e.png

 

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

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

 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 +
                 '}';
     }
 }
 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;
     }
 }
 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;
     }
 }
 // 客户端:

 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());
     }
 }

 

a1e480fa6ef445f89a370e3184a9fa0a.png

5. 深拷贝和浅拷贝

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

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

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

f8400ff2731f41ac900f4e8d77928af3.png

 

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

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

f0b0d7e26fa942c7947774f460bf95a8.png

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

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

 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();
     }
 }
 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());
     }
 }

 

7a949dd7be1940d99cf575d8092d5fa7.png

6.UML图

0fdac7ce6e7c4046be615d74afe082cb.png

 

7. 原型管理器

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

 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. 使用场景

  • 对象创建成本高:如果创建一个对象需要占用太多的资源,可以使用原型模式,避免了初始化对象所需的大部分步骤,提高性能;

  • 类实例之间区别小:如果一个类的实例之间区别较小,通过复制已有实例的数据创建新的实例,而不是通过构造函数初始化;

  • 大量相似对象的创建:在需要创建大量相似对象的情况下,原型模式可以通过复制原型对象来生成大量对象,避免了重复的初始化过程;

 

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值