原型模式详解(九)

一、定义

    用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象。原型模式是和单例模式一样很简单的一种设计模式。


二、优点

    1.性能优良

    原型模式是在内存二进制流的拷贝, 要比直接new一个对象性能好很多, 特别是要在一个循环体内产生大量的对象时, 原型模式可以更好地体现其优点。

    2.避免了反复的相同赋值操作

    例如有一个 Mail 类,现在创建了一个实例对象 mail,并设置了它的标题为 “xx通知”,假如我们还需要很多这样的同标题的 Mail 对象,那么通过原型模式直接 clone 已创建的 mail 对象,比起每次都 new 一个对象,并调用它的 setTiltle("xx通知") 要好很多。


三、实例分析

    1.假设有一个 Person 类,并实现了 Cloneable 方法

public class Person implements Cloneable {
 
    private String name;
    private ArrayList<String> hobbies;
 
    public Person(String name) {
        this.name = name;
        hobbies = new ArrayList<String>();
        System.out.println(name + "调用了构造方法");
    }
 
    public void addHobby(String hobby) {
        hobbies.add(hobby);
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public ArrayList<String> getHobbies() {
        return hobbies;
    }
 
    @Override
    protected Person clone() {
        Person clone = null;
        try {
            // 浅复制
            clone = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
 
}

    2.然后再模拟一个场景类

public class Main {
     
    public static void main(String[] args) {
        // 创建一个叫张三的人,爱好是学代码
        Person zhangsan = new Person("张三");
        zhangsan.addHobby("写代码");
        // 使用张三克隆出来一个新的人,给他取名叫李四
        Person clone = zhangsan.clone();
        clone.setName("李四");
        System.out.println(zhangsan + ", " + clone);
        System.out.println(zhangsan.getName() + "兴趣是: " + zhangsan.getHobbies());
        System.out.println(clone.getName() + "兴趣是: " + clone.getHobbies());
        // 李四的爱好不只是从张三那里克隆得到的“写代码”,他还喜欢“打游戏”
        clone.addHobby("打游戏");
        System.out.println(zhangsan.getName() + "兴趣是: " + zhangsan.getHobbies());
        System.out.println(clone.getName() + "兴趣是: " + clone.getHobbies());
    }
 
}

    3.运行结果为

张三调用了构造方法
Person@15db9742, Person@6d06d69c
张三兴趣是: [写代码]
李四兴趣是: [写代码]
张三兴趣是: [写代码, 打游戏]
李四兴趣是: [写代码, 打游戏]

    4.如上我们可以发现以下几个规律

        a.张三和李四打印的地址并不相同,说明他们已经是两个不同的对象了,实现了克隆。

        b.克隆时没有执行构造方法

        c.当我们修改克隆者的名字为“李四”时,张三的名字并没有改变。但是当我们给李四添加一个“打游戏”的爱好时,张三为什么也多了这个爱好,这并不是我们所期望的,那么这是为什么呢?


四、浅复制和深复制

    1.浅复制

    上面那种情况就是浅复制。所谓浅复制,就是对象内存在其他引用数据类型时(除 String 外),新克隆出来的对象的引用也是指向了旧的对象,如果修改其中一个,另一个也会随之被修改。就像上面我们给李四添加了一个“打游戏”的爱好,但是张三的兴趣也被一起修改了。

    2.深复制

    所谓深复制,就是复制出来的对象,其中的引用数据类型的成员变量,其引用也是指向了新的内存区域,即修改其中一个,另一个也不会随之被修改。

    3.实例分析

    修改上面的 Person 的 clone() 方法如下:

    @Override
    protected Person clone() {
        Person clone = null;
        try {
            // 深复制
            clone = (Person) super.clone();
            clone.hobbies = (ArrayList<String>) this.hobbies.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }

    运行结果为

张三调用了构造方法
Person@15db9742, Person@6d06d69c
张三兴趣是: [写代码]
李四兴趣是: [写代码]
张三兴趣是: [写代码]
李四兴趣是: [写代码, 打游戏]

    这样,修改李四的兴趣时,张三的兴趣就不会也被修改了,实现了深复制。


五、注意事项

    1.构造方法不会执行

    通过实现 Cloneable 并使用 clone() 方法产生的新对象,其构造方法不会被执行。因为 clone() 方法原理就是直接在内存中以二进制流的方式进行拷贝,重新分配了一个内存块,并不会调用到构造方法。

    2.浅复制和深复制

    默认的拷贝方法是浅复制,浅复制即是拷贝对象中存在除基本数据类型和 String 类型外的其它成员变量时,新对象的成员变量的引用还是指向的原对象的成员变量。要是想要进行深复制,可以重写 clone() 方法,并在其内实现深复制。

    3.对象的 clone 与对象内的 final 关键字是有冲突的

    要使用 clone 方法, 类的成员变量上不要增加final关键字,因为 final 修饰的变量地址是不可变的且不能重新赋值。


六、应用场景

    1.资源优化

    类的初始化需要消化非常多的资源,包括数据、硬件资源等。

    2.性能和安全要求

    new 产生一个对象时需要非常繁琐的数据准备和访问权限,则可以使用原型模式,且使用 clone() 方法还可以跳过构造方法。

    3.一个对象多个修改者

    一个对象需要提供给多个其它对象使用,且各个调用者都可能修改其中的值,则建议使用原型模式拷贝多个对象供调用者使用。


查看更多:设计模式分类以及六大设计原则

猜你喜欢

转载自blog.csdn.net/afei__/article/details/80603964