天天看点

Java设计模式之创建型模式--原型模式

一:简介

原型模式用起来很简单,我们只需实现Cloneable接口,重写clone方法即可。使用原型模式的目的,是为了避免初始化是需要消耗很多的资源,new一个对象时需要进行繁琐的数据准备。

二:例子

假设,创造一个人,设置他的性别,国籍,种类(黑人还是白人还是黄种人)。等等之类,比方说现在就这三个属性。

三:例子实现

1. 创建Type:

public class Type {
    //肤色
    private String fuse;
    //头发颜色
    private String toufa;
    public Type() {
    }
    public Type(String fuse, String toufa) {
        this.fuse = fuse;
        this.toufa = toufa;
    }
    public String getFuse() {
        return fuse;
    }
    public void setFuse(String fuse) {
        this.fuse = fuse;
    }
    public String getToufa() {
        return toufa;
    }
    public void setToufa(String toufa) {
        this.toufa = toufa;
    }
}
           

Type类很简单。

2.创建People:

这个类需要被我去拷贝,所有需要实现Cloneable接口和重写clone方法

public class People implements Cloneable {
    //性别
    private String sex;
    //国籍
    private String nationality;
    //种类
    private Type type;
    public People() {
    }
    public People(String sex, String nationality, Type type) {
        this.sex = sex;
        this.nationality = nationality;
        this.type = type;
    }
    //重写clone方法
    @Override
    protected Object clone(){
        People people = null;
        try {
            //拷贝一个人
            people = (People) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getNationality() {
        return nationality;
    }
    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
    public Type getType() {
        return type;
    }
    public void setType(Type type) {
        this.type = type;
    }
    @Override
    public String toString() {
        return "性别:"+sex+"\n国籍:"+nationality+"\n种类:\t肤色:"+type.getFuse()+"\t头发:"+type.getToufa();
    }
}
           

3.创建测试类:

public static void main(String[] args) {
        Type type = new Type("黄皮肤","黑头发");
        People people = new People();
        people.setSex("男");
        people.setNationality("中国");
        people.setType(type);
        System.out.println(people.toString());
        //第一个人创建好了,那如果我们还要创建这样的一个人呢,
        //我们只需要clone一下就行了
        People people1 = (People) people.clone();
        System.out.println("---------------------");
        System.out.println(people1.toString());
        //修改一些属性
        people1.setSex("女");
        System.out.println("---------------------");
        System.out.println(people1.toString());
    }
    结果:
    性别:男
    国籍:中国
    种类: 肤色:黄皮肤  头发:黑头发
    ---------------------
    性别:男
    国籍:中国
    种类: 肤色:黄皮肤  头发:黑头发
    ---------------------
    性别:女
    国籍:中国
    种类: 肤色:黄皮肤  头发:黑头发
           

我们可以看到,我们拷贝了一个一模一样的人。当然我们也可以动态的修改复制出来的人的一些属性。这样我们的对象不再是new出来,不过clone和new都会在内存中分配地址。并不会减少内存的开销,只不过,我们有时候用new会有繁琐的操作,即使实际上只需改其中的一点点。所以我们用clone就会很好的避免这个问题。

对象拷贝时,构造方法并不会被执行

我们修改默认构造方法:
 public People() {
        System.out.println("构造方法被调用了!");
    }
其他代码都不变
结果:
构造方法被调用了!
性别:男
国籍:中国
种类: 肤色:黄皮肤  头发:黑头发
---------------------
性别:男
国籍:中国
种类: 肤色:黄皮肤  头发:黑头发
---------------------
性别:女
国籍:中国
种类: 肤色:黄皮肤  头发:黑头发
           

我们可以看到,我们拷贝了两次,但是构造方法只是在最开始new的时候被调用了。

这是因为:

Object类的clone方法原理是从内存中(堆中)以二进制流的方法进行拷贝,重新分配一个内存块。

四:原型中的浅拷贝和深拷贝

1. 先看浅拷贝的例子

还是一个人,里面有个list

public class People implements Cloneable{
    private ArrayList<String> array = new ArrayList<String>();
    @Override
    protected People clone(){
        People people = null;
        try {
            people = (People) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    public List<String> getArray() {
        return array;
    }
    public void setValue(String value) {
        this.array.add(value);
    }
}
           

直接测试

public static void main(String[] args) {
        People people = new People();
        people.setValue("张三");
        People p2 = people.clone();
        p2.setValue("李四");
        for (String s : people .getArray())
            System.out.println(s);
    }
    结果:
    张三
    李四
           

我们会发现,people里应该只有一个张三啊,怎么被拷贝一个对象后,那个拷贝对象添加了一个李四,为什么原先的对象中也被添加了,太奇怪了。

这里我们要明白两点:

1.我们的clone方法并不是Cloneable接口的而是Object这个父类的,你可以去点进去看,发现这个接口,并没有任何方法,这个接口只是作为一个标识,只有实现改接口的类才‘有可能’被拷贝,没错是有可能!那么如何让这可能变成肯定呢,那就是重写clone方法。

2.Object提供的clone方法,只能拷贝一些原始类型如int,long…也包括String。

2.深拷贝例子

我们只需修改People中clone的代码就可以了
@Override
    protected People clone(){
        People people = null;
        try {
            people = (People) super.clone();
            //这句话,让我们的array在对象拷贝的时候也拷贝一下,这里注意我们不能用List,而是要用它的实现类ArrayList,因为接口并没有clone方法。
            people.array = (ArrayList<String>)this.array.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return people;
    }
    看看结果:
    张三
           

我们发现这次被拷贝对象并没有和拷贝对象公用属性。而是各用个的。这就是深拷贝。

五:适用场景

参考设计模式

一是类初始化需要消化非常多的资源,这个资源包括数据、硬件资源

等;

二是通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;

三是一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone的方法创建一个对象,然后由工厂方法提供给调用者。