设计模式之原型模式

1. 前言

在介绍原型模式之前,我们先举一个小例子来引出我们要讲述的内容。

在平常,如果我们想要克隆多个相同属性的对象时,我们通常会使用下面的方式进行克隆。

我们要用到的Person类

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    Person(){
        System.out.println("空参函数调用");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

当我们想要克隆多个一模一样的对象时,测试类会这样写

@Test
public void test1() throws CloneNotSupportedException {
    Person person1 = new Person("张三", 22);
    Person person2 = new Person(person1.getName(), person1.getAge());
    Person person3 = new Person(person1.getName(), person1.getAge());
    Person person4 = new Person(person1.getName(), person1.getAge());

    System.out.println(person1);
    System.out.println(person2);
    System.out.println(person3);
    System.out.println(person4);
}

这种方式的缺点很明显,就是我们每次都要获取原始对象成员变量,当对象的成员变量很多时,这种方式就显得特别麻烦效率很低

下面所讲述的原型模式就是用来解决这个问题的。

2. 原型模式

2.1 原型模式介绍

原型模式介绍

原型模式是一种创建型设计模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又可以保证性能

应用场景

当创建对象成本较大时,可以使用原型模式对已有对象进行复制来获得。

使用

实现Cloneable接口,重写clone()方法

在具体使用之前,我们需要先了解一下浅拷贝和深拷贝

对于浅拷贝来说,当成员变量引用类型时,浅拷贝拷贝的是地址;当成员变量基本类型时,浅拷贝拷贝的是

对于深拷贝来说,无论成员变量是基本类型还是引用类型,采用的都是值传递,而不是拷贝了一个地址。

2.2. 浅拷贝

2.2.1 浅拷贝例子1

下面我们举一个浅拷贝的例子:

让Person类实现Cloneable接口,重写clone()方法

package antetype;

import java.util.ArrayList;

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

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    Person(){
        System.out.println("空参函数调用");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
}

测试类

@Test
public void test2() throws CloneNotSupportedException {
    Person person1 = new Person();
    person1.setName("张三");
    person1.setAge(22);
    Person person2 = person1.clone();
    System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode());
    System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode());
    System.out.print("两个Person对象地址是否相同:");
    System.out.println(person1 == person2);
    System.out.println("修改person2:");
    person2.setName("李四");
    System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode());
    System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode());
}

结果

空参函数调用
person1 Person{name='张三', age=22} hashcode:463345942
person2 Person{name='张三', age=22} hashcode:195600860
两个Person对象地址是否相同:false
修改person2:
person1 Person{name='张三', age=22} hashcode:463345942
person2 Person{name='李四', age=22} hashcode:195600860

从结果中我们可以看到,person2是对person1的复制,两个对象的成员变量的值一模一样,且两个对象的地址不同,当我们修改克隆生成的person2时,我们可以发现person1的成员变量并没有改变,因此对于类型是基本类型成员变量来说,拷贝的是,而不是拷贝了一个地址,且拷贝的新成员变量并不是经过构造方法拷贝出的。

2.2.2 浅拷贝例子2

接下来我们说一下浅拷贝的例子2,我们在Person类中加一个List集合。

Person类

public class Person implements Cloneable{
    private String name;
    private int age;
    private ArrayList list;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    Person(){
        System.out.println("空参函数调用");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public ArrayList getList() {
        return list;
    }

    public void setList(ArrayList list) {
        this.list = list;
    }

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

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
}

测试类

@Test
public void test3() throws CloneNotSupportedException {
    Person person1 = new Person();
    person1.setName("张三");
    person1.setAge(22);
    person1.setList(new ArrayList());
    Person person2 = person1.clone();
    List list1 = person1.getList();
    list1.add(111);
    list1.add(222);
    System.out.println("person1" + " " + person1 + " " + "hashcode:" + person1.hashCode() + " " + "list" + person1.getList());
    System.out.println("person2" + " " + person2 + " " + "hashcode:" + person2.hashCode() + " " + "list" + person1.getList());
    System.out.print("两个Person对象地址是否相同:");
    System.out.println(person1 == person2);
    System.out.print("两个Person对象中的list对象的地址是否相同:");
    System.out.println(person1.getList() == person2.getList());
    System.out.println("修改person2中的List:");
    List list2 = person2.getList();
    list2.add(333);
    System.out.println("修改后两个对象中的list再次比较:");
    System.out.println("person1的list" + person1.getList());
    System.out.println("person2的list" + person2.getList());
    System.out.print("两个对象的list地址是否相同:");
    System.out.println(person1.getList() == person2.getList());
}

结果

空参函数调用
person1 Person{name='张三', age=22, list=[111, 222]} hashcode:463345942 list[111, 222]
person2 Person{name='张三', age=22, list=[111, 222]} hashcode:195600860 list[111, 222]
两个Person对象地址是否相同:false
两个Person对象中的list对象的地址是否相同:true
修改person2中的List:
修改后两个对象中的list再次比较:
person1的list[111, 222, 333]
person2的list[111, 222, 333]
两个对象的list地址是否相同:true

结论

从上面的执行结果中我们其实可以得出以下结论:

当我们对Person对象进行克隆时,可以看成是new了一个Person对象(其实并没有调用构造方法),对于基本类型的成员变量采用的是值传递,拷贝的是值,而对于引用类型的成员变量浅拷贝仅仅拷贝的是一个地址,对象之间共享这个引用类型的成员变量。

2.3 深拷贝

方法一

对于引用类型的成员变量也实现拷贝就好了,也就是无限递归下去,不再赘述,可以参考下面博客的链接。

方法二

通过对象序列化和反序列化首先深拷贝(推荐)

深拷贝

首先Person对象实现Serializable接口,然后自定义拷贝方法deepClone()

Person类

package antetype;

import jdk.nashorn.internal.ir.SplitReturn;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class Person implements Cloneable, Serializable {
    private String name;
    private int age;
    private ArrayList list;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    Person(){
        System.out.println("空参函数调用");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public ArrayList getList() {
        return list;
    }

    public void setList(ArrayList list) {
        this.list = list;
    }

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

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person)super.clone();
    }
    //深拷贝
    public Person deepClone(){
        // 创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            Person person = (Person) ois.readObject();
            return person;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

}

测试类

@Test
    public void test4() throws CloneNotSupportedException {
        Person person1 = new Person();
        person1.setName("张三");
        person1.setAge(22);
        person1.setList(new ArrayList());
        Person person2 = person1.deepClone();
        System.out.print("两个对象的list地址是否相同:");
        System.out.println(person1.getList() == person2.getList());
    }

结果

空参函数调用
两个对象的list地址是否相同:false

3. 总结

  • 创建新的对象比较复杂时,可以使用原型模式简化对象的创建过程,同时也可以提升效率
  • 如果原始对象发生变化(增加或减少属性),其他克隆对象也会发生相应的变化,无需修改代码。
  • 如果成员变量没有引用类型只使用浅拷贝即可;如果引用类型的成员变量很少,可考虑递归实现clone否则推荐序列化

学习于:
设计模式-原型模式(克隆羊多利看了都说好)
初学Java常用设计模式之——原型模式
序列化和反序列化的详解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值