设计模式之原型模式

67 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

简介

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

通俗地讲就是现在已有一个对象,但是现在又要创建另外一个一摸一样的对象时,我会使用clone方法来快速地将已有的对象复制一份。需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。

原型模式适用于以下几种场景下使用:

  • 需要生成一大批很相像的类的对象时,不用每次去做重复的赋值工作
  • 一个对象多个修改者的场景
  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗

原型模式在理解上是非常简单的。就是通过复制来创建对象,但是要彻底弄清楚复制(clone)里的玄机和细节还是需要花点功夫的,下面我们通过一个例子来简单分析下。

案例

我们通过一个简单的例子,即Person类的复制来说明原型模式。

首先我们定义一个Person类,属性有姓名、出生日期(注意这2个属性的区别)

public class Person implements Cloneable {
    public Person(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }
    private String name;
    private Date birthday;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", birthday=" + birthday +
                '}';
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

这个Person的定义我觉得大家应该都没什么疑问,除了最后一个重新的clone方法大家可能见的不多,这个方法就是用来复制自己的,它是Object类就有的对象。在Object里它定义为一个native方法,即和底层打交道的方法,我们这里不过多细究,先用起来。

然后是一个应用类:

public class App {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        Person zhangsan = new Person("张三", date);
        Person zhangsan1 = (Person)zhangsan.clone();
        System.out.println("1:"+zhangsan);
        System.out.println("2:"+zhangsan1);
        System.out.println("3:"+(zhangsan == zhangsan1));
        System.out.println("4:"+(zhangsan.getBirthday() == zhangsan1.getBirthday()));
        zhangsan.setName("李四");
        date.setTime(2345678);
        System.out.println("5:"+zhangsan);
        System.out.println("6:"+zhangsan1);
        System.out.println("7:"+(zhangsan == zhangsan1));
        System.out.println("8:"+(zhangsan.getBirthday() == zhangsan1.getBirthday()));
    }
}

输出情况如下: 图片.png 我们通过结果可以清晰地看到3和4位置输出的结果分别是false和true,说明复制的对象和原对象的分别指向了不同的内存区域,但是birthday属性指向的却是相同的一块区域。说明什么,说明我们拷贝只是拷贝了外层的对象和对象内的值类型的数据,而引用型的数据是不会被拷贝的。

这种拷贝叫做浅拷贝

那很显然我们真实场景中大部分是不想使用浅拷贝的,我们要全部拷贝一份,成为一个独立的对象,那我们就需要深拷贝了。

我们将clone方法修改下,如下:

@Override
public Object clone() throws CloneNotSupportedException {
    Person p = (Person)super.clone();
    Date d = (Date)this.birthday.clone();
    p.setBirthday(d);
    return p;
}

同样运行App应用类,结果如下: 图片.png 如上,我们如愿以偿,实现了深拷贝。

总结

从上面的实践我们可以看到,原型模式的确在复杂对象的创建方面提供了很大的方便,但是其缺点也是比较明明显,因为存在浅拷贝和深拷贝的区别在里面,在使用时候很容易忽略这点,而造成bug。而且我们大多数情况下要用的深拷贝在引用对象多的情况下,实现深拷贝代码量也是不小的,这点使用的时候要权衡。