设计模式之原型模式

原型模式

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

类图

原型模式的优缺点

优点:

  • 原型模式性能比直接new一个对象性能高,是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。
  • 简化创建过程。
  • 保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

缺点:

  • 首先要记住原型模式的拷贝时不会执行构造函数的。
  • clone并不一定比new一个对象快,只有当new对象比较耗时时,才考虑使用原型模式。
  • 必须配备克隆方法。
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险。
  • 深拷贝、浅拷贝要运用得当。
  • 要使用clone方法,类的成员变量上不要增加final关键字,final类型是不允许重赋值的。
原型模式的应用场景
  • 对象之间相同或相似,即只是个别的几个属性不同的时候。
  • 对象的创建过程比较麻烦,但复制比较简单的时候。
原型模式的实现

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
public class Prototype implements Cloneable {
    private String name;

    private List<String> names;

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

    public List<String> getNames() {
        return names;
    }

    public void setNames(List<String> names) {
        this.names = names;
    }

    public String getName() {
        return name;
    }

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

测试

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype prototype = new Prototype();
        prototype.setName("原型");
        List<String> names = new ArrayList<>();
        names.add("原型");
        prototype.setNames(names);
        for (int i = 0; i < 5; i++){
            Prototype p = (Prototype) prototype.clone();
            p.setName("测试" + i);
            p.getNames().add("测试" + i);
            System.out.println(p.toString());
            System.out.println(p.getName());
            System.out.println(p.getNames().size());
        }
        System.out.println(prototype.toString());
        System.out.println(prototype.getName());
        System.out.println(prototype.getNames().size());
    }
}

输出

com.example.demo.prototypePattern.Prototype@681a9515
测试0
2
com.example.demo.prototypePattern.Prototype@3af49f1c
测试1
3
com.example.demo.prototypePattern.Prototype@19469ea2
测试2
4
com.example.demo.prototypePattern.Prototype@13221655
测试3
5
com.example.demo.prototypePattern.Prototype@2f2c9b19
测试4
6
com.example.demo.prototypePattern.Prototype@31befd9f
原型
6

通过输出结果可以看出:

  • 每个对象的地址是不同的
  • 修改基本类型时,并不能影响基础类,而引用对象只是指向的基础类的属性。
原型模式对单例模式的破坏

当单例对象实现了clone方法时,会返回多个实例,请看实现:

/**
 * 简单的饿汉式单例
 */
public class StaticInnerClassSingleton implements Cloneable {

    /**
     * 看静态类的初始化锁那个线程可以拿到
     */
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton() {
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例对象禁止反射调用");
        }
    }

    /**
     * 直接重写clone方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        //获取单例对象
        StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
        System.out.println(singleton.toString());

        //clone获取克隆对象
        StaticInnerClassSingleton singleton1 = (StaticInnerClassSingleton) singleton.clone();
        System.out.println(singleton1.toString());
    }
}

输出日志

com.example.demo.prototypePattern.StaticInnerClassSingleton@1540e19d
com.example.demo.prototypePattern.StaticInnerClassSingleton@677327b6

根据日志可以看出,单例模式被破坏掉。
重写clone()方法,直接返回INSTANCE对象解决原型模式对单例模式的破坏

/**
 * 简单的饿汉式单例
 */
public class StaticInnerClassSingleton1 implements Cloneable {

    /**
     * 看静态类的初始化锁那个线程可以拿到
     */
    private static class InnerClass {
        private static StaticInnerClassSingleton1 staticInnerClassSingleton = new StaticInnerClassSingleton1();
    }

    public static StaticInnerClassSingleton1 getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }

    private StaticInnerClassSingleton1() {
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例对象禁止反射调用");
        }
    }

    /**
     * 修改克隆方法,返回单例对象
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return InnerClass.staticInnerClassSingleton;
    }
}
/**
 * 修改后的测试类
 */
public class Test1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        //获取单例对象
        StaticInnerClassSingleton1 singleton1 = StaticInnerClassSingleton1.getInstance();
        System.out.println(singleton1.toString());

        //获取clone对象
        StaticInnerClassSingleton1 singleton2 = (StaticInnerClassSingleton1) singleton1.clone();
        System.out.println(singleton2.toString());
    }
}

输出日志

com.example.demo.prototypePattern.StaticInnerClassSingleton1@1540e19d
com.example.demo.prototypePattern.StaticInnerClassSingleton1@1540e19d

可以看出,返回的对象地址时一致的。这样就解决了原型对单例模式的破坏。

拓展:深拷贝与浅拷贝

浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
实现深拷贝的方法:

  • 让每个引用类型属性内部都重写clone() 方法
  • 利用序列化,序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。注意每个需要序列化的类都要实现 Serializable接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值