【设计模式】原型模式

原型模式属于创建型模式,主要作用是利用一个原型对象的克隆方法,在保证性能的情况下创建多个重复的对象,本质就是通过克隆一个原有的对象来复制出一个新对象。

原型模式的介绍

​ 原型模式主要是为了解决创建一个新对象时产生的大量资源消耗(如硬件资源或网络资源)问题,它首先记录一个创建好的对象作为原型对象,之后需要创建新对象时直接通过该原型对象的克隆方法快速的复制出一个新对象,这样就跳过了对象的初始化过程,自然减少了初始化的资源消耗。当创建新对象的成本比较大,而同类型的对象间差别不大时,就可以使用原型模式来创建。

使用场景

  • 在循环体中创建大量对象
  • 初始化时需要消耗大量硬件或网络资源
  • 构建对象时需要进行大量的数据准备和权限校验操作
  • 一个对象存在多个访问者,而这些访问者都要修改其属性,此时考虑到性能与安全问题,可以使用原型模式来复制出多个该对象的副本供访问者修改,之后再通过复制对象来修改原型对象。

原型模式的实现

​ 在JAVA中可以使用Object clone()方法实现对象的克隆操作,但是,一个类需要实现Cloneable接口才能使用克隆方法,否则会报CloneNotSupportedException 异常。在此我将创建一个Sheep类,并要求循环创建一百万对象,为节省资源消耗,我决定使用原型模式进行实现。

类图

image-20221122105151685

实现方法

第一步,编写Sheep类,实现Cloneable接口

Sheep
package 设计模式.原型模式;

public class Sheep implements Cloneable {
    private String name;
    private String color;
    private int age;


    public Sheep(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "羊";
            name = name.substring(name.length()-1);
        }
    }

    @Override
    protected Sheep clone(){
        try {
            return (Sheep) super.clone();
        }catch (CloneNotSupportedException e){
            // 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
            System.out.println("克隆失败, 可能是未实现Cloneable接口!");
        }
        return null;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}';
    }
}

第二步,编写Test类测试

Test
package 设计模式.原型模式;

public class Test {
    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000 ; i++){
            Sheep sheep = new Sheep("Dolly","white", 5);
        }
        System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
        
		// 准备一个原型对象
        Sheep prototypeObj =  new Sheep("Dolly","white", 5);
        time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000 ; i++){
            Sheep sheep = prototypeObj.clone();
        }
        System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
    }
}

测试结果

image-20221122104930773




原型模式的扩展

​ 原型模式还可以扩展为带有原型管理器的原型模式,并且克隆方法具有浅拷贝深拷贝的区别。

带原型管理器的原型模式

​ 原型管理器可以管理多个类的原型对象,方便这些类通过原型模式创建对象。其原理是通过一个HashMap集合来管理每个原型对象,同时,为符合依赖倒置原则,多个原型类应当具实现同一个接口类,而管理器只需要对这个接口类进行管理即可。

类图

images202211221627593.png

第一步,编写Animal接口

Animal
package 设计模式.原型模式.原型管理器;

/**
 * 继承克隆接口,这样实现该接口的类都能正常使用克隆方法
 */
public interface Animal extends Cloneable {
    Animal clone();
    String toString();
}

第二步,编写Sheep、Cat、Dog类

Sheep
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

public class Sheep implements Animal {
	…… 内部属性与方法都和之前写的一样
}
Cat
package 设计模式.原型模式.原型管理器;

public class Cat implements Animal {
    private String name;
    private String color;
    private int age;

    public Cat(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "猫";
            name = name.substring(name.length()-1);
        }
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public Cat clone() {
        try {
            return (Cat) super.clone();
        } catch (CloneNotSupportedException e){
        }
        return null;
    }
}
Dog
package 设计模式.原型模式.原型管理器;

public class Dog implements Animal {
    private String name;
    private String color;
    private int age;

    public Dog(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "狗";
            name = name.substring(name.length()-1);
        }
    }

    @Override
    public Dog clone() {
        try {
            return (Dog) super.clone();
        } catch (CloneNotSupportedException e) {
        }
        return null;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}';
    }
}

第三步,编写PrototypeManager

PrototypeManager
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

import java.util.HashMap;

public class PrototypeManager {

    private HashMap<String, Animal> prototypeMap = new HashMap<>();

    public PrototypeManager(){
        put("cat", new Cat("Tom", "black", 7));
        put("dog", new Dog("福贵", "黑白相间", 23));
        put("sheep", new Sheep("Dolly","white", 5));
    }

    public void put(String key, Animal value){
        prototypeMap.put(key, value);
    }

    public Animal get(String key){
        Animal animal = prototypeMap.get(key);
        if (animal != null){
            return animal.clone();
        }
        return null;
    }
}

第四步,编写Test测试

Test
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class Test {
    public static void main(String[] args) {
        PrototypeManager prototypeManager = new PrototypeManager();
        Cat cat = null;
        Dog dog = null;
        Sheep sheep = null;
        long time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000; i++) {
            cat = new Cat("Tom", "black", 7);
            dog = new Dog("Dog", "white", 5);
            sheep = new Sheep("Dolly", "white", 5);
        }
        System.out.println("通过构造方法new:");
        System.out.println(cat);
        System.out.println(dog);
        System.out.println(sheep);
        System.out.println("新建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");

        time = System.currentTimeMillis();
        // 模拟在循环体中大量创建对象
        for (int i = 0; i < 1000000; i++) {
            cat = (Cat) prototypeManager.get("cat");
            dog = (Dog) prototypeManager.get("dog");
            sheep = (Sheep) prototypeManager.get("sheep");
        }
        System.out.println("原型管理器创建:");
        System.out.println(cat);
        System.out.println(dog);
        System.out.println(sheep);
        System.out.println("原型模式创建对象运行时间:" + (System.currentTimeMillis() - time) + "ms");
    }
}
测试结果

image-20221122114512138




浅拷贝与深拷贝

​ 原型模式下可以分为浅拷贝和深拷贝。Java中存在基础类型引用类型两种数据,浅拷贝在拷贝时会复制基础类型的值到新的变量中,但是,引用类型的数据是地址,就算把值复制到新的变量,地址值也还是指向原来的数据空间,所以浅拷贝下,引用数据不会被克隆,而是直接引用。若需改变引用地址,需要使用深拷贝。

浅拷贝试验

​ 浅拷贝无法为引用型属性复制一份新的数据地址空间,只是创建一个新的变量空间,然后将旧的引用数据地址复制给该变量。

试验方法
Sheep类
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

public class Sheep implements Animal {
    public String name;
    public String color;
    public int age;


    public Sheep(String name, String color, int age) {
        this.name = name;
        this.color = color;
        this.age = age;
        // 模拟对构造方法进行数据准备操作,不做的话JVM会对new方法进行优化,那样new的效率肯定高于clone
        if (name != null){
            name += "羊";
            name = name.substring(name.length()-1);
        }
    }
	
    /**
     * 浅拷贝
     * @return 通过浅拷贝复制的新对象
     */
    @Override
    public Sheep clone(){
        try {
            return (Sheep) super.clone();
        }catch (CloneNotSupportedException e){
            // 没有实现Cloneable接口还调用clone()方法会抛CloneNotSupportedException异常
            System.out.println("克隆失败, 可能是未实现Cloneable接口!");
        }
        return null;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", age=" + age +
                '}';
    }
}
CloneTest类
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class CloneTest {
    public static void main(String[] args) {
        // 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
        Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
        Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
        // 使用浅拷贝创建一个对象
        Sheep sheep3 = sheep.clone();

        System.out.println("sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
        System.out.println("sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
        System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
        System.out.println("sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
        System.out.println("sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
        System.out.println("因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));

    }
}
试验结果

在这里插入图片描述

从截图结果来看,使用浅拷贝创建的新对象,所持有的引用型变量(String类型的name属性)于原型对象的引用型变量地址是相同的,那么就证明浅拷贝并不会复制一个新的引用型数据。



深拷贝试验

​ 在Java中,深拷贝需要使用序列化与反序列化来实现。通过序列化写入到流中的对象,不仅其本身要实现序列化,而且它所有的引用型属性也要实现序列化操作,此时通过序列化复制到流中的对象,不仅克隆了自身也克隆了它的所有引用属性。当把此对象从流中反序列化读取出来时,就已经完成了深拷贝。

试验方法
Sheep类
package 设计模式.原型模式;

import 设计模式.原型模式.原型管理器.Animal;

import java.io.*;

public class Sheep implements Animal,Serializable {
    
    /**
     * 深拷贝
     * @return 通过深拷贝复制的对象
     */
    public Sheep deepClone(){
        // 写入流的对象必须实现Serializable序列化接口,其引用型属性也要实现,这里的引用型属性只有String类型,已经自动实现过序列化接口
        ByteArrayOutputStream bos = null;
        ByteArrayInputStream bis = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        Sheep sheep = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            sheep =  (Sheep) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
          try {
              bos.close();
              bis.close();
              oos.close();
              ois.close();
          } catch (IOException e) {
          }
        }
        return sheep;
    }
    
    …… 其他属性和方法与浅拷贝的Sheep类相同

}

CloneTest类
package 设计模式.原型模式.原型管理器;

import 设计模式.原型模式.Sheep;

public class CloneTest {
    public static void main(String[] args) {
        // 使用new String()可以创建一个新的引用对象,以此来区分浅拷贝下的引用变量是否被重新创建,但使用字面量都会指字符串常量池中的地址
        Sheep sheep = new Sheep(new String("Dolly"), "white", 5);
        Sheep sheep2 = new Sheep(new String("Dolly"), "white", 5);
        // 使用浅拷贝创建一个对象
        Sheep sheep3 = sheep.clone();
        // 使用深拷贝创建一个对象
        Sheep sheep4 = sheep.deepClone();
        System.out.println("普通新建对象:");
        System.out.println("    sheep和sheep2是否指向同一个地址:" + (sheep == sheep2));
        System.out.println("    sheep的引用属性和sheep2的引用属性是否指向同一个地址:" + (sheep.name == sheep2.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep2.color));
        System.out.println("浅拷贝:");
        System.out.println("    sheep和sheep3是否指向同一个地址:" + (sheep == sheep3));
        System.out.println("    sheep的引用属性和sheep3的引用属性是否指向同一个地址:" + (sheep.name == sheep3.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep3.color));
        System.out.println("深拷贝:");
        System.out.println("    sheep和sheep4是否指向同一个地址:" + (sheep == sheep4));
        System.out.println("    sheep的引用属性和sheep4的引用属性是否指向同一个地址:" + (sheep.name == sheep4.name));
        System.out.println("    因为使用了字面量,肯定都指向常量池中的地址:" + (sheep.color == sheep4.color));

    }
}
试验结果

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值