原型模式

198 阅读4分钟

一.作用

原型模式用于创建重复的对象,实现对象的拷贝。

二.演变过程

2.1 直接复制

要说道复制一个对象,对我们来说最简单也最直观的方式可能就是在知道对象的属性以后直接new一个新的对象即可。代码如下:

//  需要被克隆的羊
public class Sheep {
    private String name;
    private int age;

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

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

克隆开始

Sheep sheep = new Sheep("Ana",2);
Sheep sheep2 = new Sheep("Ana",2);
Sheep sheep3 = new Sheep("Ana",2);

这样做很麻烦,我们需要提前知道对象的属性,并且如果字段多的话,设置属性都会累死人。

2.2 使用Object类提供的clone()方法

clone方法可以将Java对象复制一份,但是需要实现clone的java类必须要实现Cloneable接口,该接口表示该类能够复制且具有复制的能力。

修改后的克隆羊类:

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

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

    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
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

这个时候我们再需要克隆羊应该怎么做?

Sheep sheep = new Sheep("Ana",2);
Sheep sheep1 = (Sheep)sheep.clone();
Sheep sheep2 = (Sheep)sheep.clone();

System.out.println(sheep);
System.out.println(sheep1);
System.out.println(sheep2);

结果如下

image.png

这样是不是就方便多了,代码也更加的优雅呢?但是现在还有一个问题,我们只能复制基本数据类型,如果需要克隆的类中含有引用类型的数据,那么就克隆出来的将只是一个引用,不是一个新的引用类型。

如下:

public class Sheep implements Cloneable {
    private String name;
    private int age;
 
    public Sheep friend;   //克隆羊多了一个羊朋友

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

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

    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
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

再进行克隆试试

Sheep sheep = new Sheep("Ana",2);
sheep.friend = new Sheep("Tom",1);
Sheep sheep1 = (Sheep)sheep.clone();
Sheep sheep2 = (Sheep)sheep.clone();

System.out.println(sheep + "sheep.friend: "+sheep.friend.hashCode());
System.out.println(sheep1+ "sheep1.friend: "+sheep1.friend.hashCode());
System.out.println(sheep2+ "sheep2.friend: "+sheep2.friend.hashCode());

结果如下

image.png

可以发现,克隆出来的三个羊朋友hashcode都相同,说明是一个对象。这并不是我们希望得到的效果。为什么会出现这个情况呢?这就涉及到了一个深拷贝和浅拷贝的问题。

浅拷贝

对于基本数据类型,浅拷贝直接进行值传递,也就是复制一份相同的值;对于引用数据类型只会创建一个新的引用,但是不会去申请一块新的内存空间,这个新的引用还是指向原来那个引用所指向的内存地址。

深拷贝

对于基本数据类型,深拷贝直接复制一份相同的值;对于引用数据类型,不仅会创建一个新的引用,还会申请一块新的内存地址,同时让这个新的引用指向这个新的内存地址。

这是我们已经很清楚了,我们想要实现的是深拷贝!因为如果要拷贝的对象含有引用类型的话我们是也要申请一块新的内存空间的,如何实现?

两种方式:序列化 和 重写 clone方法

序列化

在需要克隆的对象中自定义一个克隆的方法,注意,要克隆的类必须实现Serilizable接口,所依赖的类也需要实现序列化接口。

//该方法用于本类的克隆
public Object 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);
        DeepPrototype copyObj = (DeepPrototype)ois.readObject();

        return copyObj;

    } catch (Exception e) {
        e.printStackTrace();
        return null;
    } finally {
        //关闭流
        try {
            bos.close();
            oos.close();
            bis.close();
            ois.close();
        } catch (Exception e2) {

            System.out.println(e2.getMessage());
        }
    }
重写clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
	
	Object deep = null;
	//这里完成对基本数据类型(属性)和String的克隆
	deep = super.clone(); 
	//对引用类型的属性,进行单独处理
	DeepProtoType deepProtoType = (DeepProtoType)deep;
	deepProtoType.deepCloneableTarget  = (DeepCloneableTarget)deepCloneableTarget.clone();
	
	// TODO Auto-generated method stub
	return deepProtoType;
}
    

至此,原型模式结束。感谢大家的收看。创作不易,如有帮助,请一键三连。