设计模式之原型模式

710 阅读3分钟

指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新 的对象,属于创建型模式。 说白了就是拷贝创建对象,不通过调用构造方法.

优点:

  • 性能优良 ,Java 自带的 原型模式 是基于内存二进制流的拷贝,比直接 new 一个对象性能上提 升了许多
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化 了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点:

  • 需要为每一个类配置一个克隆方法
  • 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。
  • 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克 隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得 当

使用场景

为什么需要原型模式?我们直接通过new不好了吗?
比如如果一个类的属性有30个,这时候你怎么办? 通过构造方法创建?,那万一有些是可选字段,有些是必选呢?,这时候就要创建很多构造方法。

  • 类初始化消耗资源较多
  • new产生一个对象需要非常繁琐的过程(比如数据准备,访问权限)
  • 构造函数比较复杂
  • 循环体中产生大量对象

在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用的 JSON.parseObject()也是一种原型模式。

浅克隆

Demo

 JDK 已经帮我们实现了一个现 成的 API,我们只需要实现 Cloneable 接口即可。

public class Parkin implements Cloneable{
    private String name;
    private int age;

    private List<String> hobbies = new ArrayList<>();

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

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

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

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


// 测试类
public class ShadowClone {
    public static void main(String[] args) throws CloneNotSupportedException {
        Parkin parkin1 = new Parkin();
        Parkin parkin2 = (Parkin) parkin1.clone();

        System.out.println(parkin1 == parkin2); // false 证明不是同一个对象

        parkin1.setAge(23);
        parkin1.setName("Parking1");

        parkin2.setAge(12);
        parkin2.setName("Parking2");


        /**
         * 上面值改了,还是改的自己的
         */
        System.out.println(parkin1);
        System.out.println(parkin2);

        /**
         * 结果为true
         * 引用的是同一个对象
         *
         */
        System.out.println(parkin1.getHobbies() == parkin2.getHobbies());

        /**
         * 发现基本类型可以改变,引用类型的还是同一个对象
         * 那String也是引用类型啊,为什么他也变了呢?
         * String是不可变对象,因为是final修饰的,所以他会重新创建一个
         *
         */

    }
}

深克隆

如何解决上面那个引用的对象没变呢?这就需要用到深克隆了。

深克隆有2种实现方式

  • 序列化
  • JSON

Demo

public class Parkin implements Serializable {
    private String name;
    private int age;

    private List<String> hobbies = new ArrayList<>();

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

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

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

// 测试类
 public static void main(String[] args) throws IOException, ClassNotFoundException {

        Parkin parkin1 = new Parkin();

        // 1.第一种方式:序列化  也可以使用工具类
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(parkin1);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Parkin parkin2 = (Parkin) objectInputStream.readObject();

        System.out.println(parkin1 == parkin2);
        System.out.println(parkin1.getHobbies() == parkin2.getHobbies());


        // 2. 第二种方式:使用JSON
        Parkin parkin3 = JSON.parseObject(JSON.toJSONString(parkin1), Parkin.class);
        System.out.println(parkin1 == parkin3);
        System.out.println(parkin1.getHobbies() == parkin3.getHobbies());
    }

克隆破坏单例

Demo

public class Singleton implements Cloneable{
    private static final Singleton SINGLETON = new Singleton();

    private Singleton(){}

    public static Singleton getInstance() {
        return SINGLETON;
    }

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


//测试类
 public static void main(String[] args) throws CloneNotSupportedException {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = (Singleton) singleton1.clone();
        System.out.println(singleton1 == singleton2);

        /**
         * 运行结果为false,违背了单例
         * 解决方法:
         *  1. 不实现Cloneable接口
         *  2. 在clone方法里不调用父类的clone方法,直接返回SINGLETON实例
         */
    }

JDK中的运用

可以看到ArrayList实现了cloneable接口。看先clone方法。