设计模式之原型模式

原型模式的介绍

原型模式我们也称为克隆模式,即根据某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深度克隆:

  • 浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,(浅克隆)浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。 Object类提供的方法clone只是拷贝本对象 , 其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址。看下图:v1(原型对象),v2(v2是由v1克隆出来的)里面的一个引用类型的变量是指向的同一块内存地址,要修改这个变量的话,v1,v2都会被改动

    在这里插入图片描述

  • 深克隆:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。看下图:通过v1克隆v2时,把里面的变量属性也克隆了一份,并分配了内存空间。

在这里插入图片描述

运用场景

如果需要短时间创建大量对象的情况下,原型模式就是我们可以考虑实现的方式。
因为在Java中通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),这就意味着这个过程是很耗费资源与时间的。下面也有两者的对比。

原型模式之浅克隆

package com.wlw.prototype.qianCopydemo01;

import java.util.Date;

/*
原型模式的实现,就是依靠克隆,先创建出一个对象v1,通过v1 克隆出 v2
实现克隆:
    1.实现接口Cloneable
    2.重写clone()方法
 */
//视频类
public class Video implements Cloneable {
    private String name;
    private Date date;

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

    public Video() {
    }

    public Video(String name, Date date) {
        this.name = name;
        this.date = date;
    }

    //get set toString 省略.....
}

package com.wlw.prototype.qianCopydemo01;

import java.util.Date;

public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        //创建原型对象v1
        Video v1 = new Video("java",date);
        System.out.println("v1=>"+v1);
        System.out.println("v1.hashcode=>"+v1.hashCode());

        //通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
        Video v2 = (Video)v1.clone();
        System.out.println("v2=>"+v2);
        System.out.println("v2.hashcode=>"+v2.hashCode());
    }
}
/*
v1=>Video{name='java', date=Tue Oct 20 15:41:25 CST 2020}
v1.hashcode=>21685669
v2=>Video{name='java', date=Tue Oct 20 15:41:25 CST 2020}
v2.hashcode=>2133927002
*/
  • 看一下浅克隆的问题:虽然产生了两个完全不同的对象,但是被复制的对象里的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
  • 看下面的例子,v2是由v1复制出来的,而Video类里有个Date类型的变量,此时v1与v2这两个对象里的Date类型的变量是同一个,改变一个,另一个也会跟着改变。
package com.wlw.prototype.qianCopydemo01;

import java.util.Date;

public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        //创建原型对象v1
        Video v1 = new Video("java",date);
        //通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
        Video v2 = (Video)v1.clone();

        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
        System.out.println("=========================");
        date.setTime(22135321);
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
    }
}
/*
v1=>Video{name='java', date=Tue Oct 20 15:45:59 CST 2020}
v2=>Video{name='java', date=Tue Oct 20 15:45:59 CST 2020}
=========================
v1=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
v2=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
date更新之后,v1,v2里面的date全都更新了,这就是v1,v2指向了同一个date
*/

原型模式之深克隆

实现深克隆,有三种途径:

  1. 让每个引用类型属性内部都重写clone() 方法
  2. 使用序列化流
  3. 使用开源工具类(本质上和第二种是一样的,都是通过序列化)

这里实现深克隆使用的方式是:让每个引用类型属性内部都重写clone()方法。
另外两种可参考:java中赋值语句(=)与浅拷贝与深拷贝弄出来的对象有什么区别

package com.wlw.prototype.shenCopydemo02;

import java.util.Date;

/*
原型模式的实现,就是依靠克隆,先创建出一个对象v1,通过v1 克隆出 v2
实现浅克隆:
    1.实现接口Cloneable
    2.重写clone()方法
而要实现深克隆,有三种方式:
	1.让每个引用类型属性内部都重写clone() 方法
	2.使用序列化流
	3.使用开源工具类	
	
这里使用的是第一种方式:让每个引用类型属性内部都重写clone()方法
 */
//视频类
public class Video implements Cloneable {
    private String name;
    private Date date;

    //深克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        //深克隆
        Video v = (Video) obj;
        v.date = (Date)this.getDate().clone();//把对象的属性也克隆一份

        return obj;
    }

    public Video() {
    }

    public Video(String name, Date date) {
        this.name = name;
        this.date = date;
    }

    //get set toString 省略.....
}

package com.wlw.prototype.shenCopydemo02;

import java.util.Date;

public class BiliBili {
    public static void main(String[] args) throws CloneNotSupportedException {
        Date date = new Date();
        //创建原型对象v1
        Video v1 = new Video("java",date);
        //通过v1 克隆出 v2 (v2和v1是完全一样的,除了hashCode,因为v2也是一个新的对象)
        Video v2 = (Video)v1.clone();

        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
        System.out.println("=========================");
        date.setTime(22135321);
        System.out.println("v1=>"+v1);
        System.out.println("v2=>"+v2);
    }
}
/*
v1=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
v2=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
=========================
v1=>Video{name='java', date=Thu Jan 01 14:08:55 CST 1970}
v2=>Video{name='java', date=Tue Oct 20 18:18:59 CST 2020}
只有v1的时间改变了,v2的时间并没有改变
*/

原型模式和直接new对象方式的比较

当我们需要大量的同一类型对象的时候可以使用原型模式,下面是两种方式的性能对比:

用两种方式同时生成10个对象:

/**
 * 测试普通new方式创建对象和clone方式创建对象的效率差异!
 * 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
 */
public class Test {
	
	public static void testNew(int size){
		long start = System.currentTimeMillis();
		for(int i=0;i<size;i++){
			User t = new User();
		}
		long end = System.currentTimeMillis();
		System.out.println("new的方式创建耗时:"+(end-start));
	}
	
	public static void testClone(int size) throws CloneNotSupportedException{
		long start = System.currentTimeMillis();
		User t = new User();
		for(int i=0;i<size;i++){
			User temp = (User) t.clone();
		}
		long end = System.currentTimeMillis();
		System.out.println("clone的方式创建耗时:"+(end-start));
	}
	
	
	public static void main(String[] args) throws Exception {	
		testNew(10);
		testClone(10);
	}
}


//用户类
class User implements Cloneable {  
	public User() {
		try {
      //模拟创建对象耗时的过程!
			Thread.sleep(10);  
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
    // 直接调用object对象的clone()方法!
		Object obj = super.clone();  
		return obj;
	}
}

/*
输出结果:
new的方式创建耗时:108
clone的方式创建耗时:11
*/

用两种方式同时生成1000个对象:

/**
 * 测试普通new方式创建对象和clone方式创建对象的效率差异!
 * 如果需要短时间创建大量对象,并且new的过程比较耗时。则可以考虑使用原型模式!
 */
public class Test {
	
	public static void testNew(int size){
		long start = System.currentTimeMillis();
		for(int i=0;i<size;i++){
			User t = new User();
		}
		long end = System.currentTimeMillis();
		System.out.println("new的方式创建耗时:"+(end-start));
	}
	
	public static void testClone(int size) throws CloneNotSupportedException{
		long start = System.currentTimeMillis();
		User t = new User();
		for(int i=0;i<size;i++){
			User temp = (User) t.clone();
		}
		long end = System.currentTimeMillis();
		System.out.println("clone的方式创建耗时:"+(end-start));
	}
	
	
	public static void main(String[] args) throws Exception {	
		testNew(1000);
		testClone(1000);
	}
}

/*
new的方式创建耗时:10836
clone的方式创建耗时:11
*/

小结:在短时间创建大量对象的情况下, 通过clone的方式性能开销基本没有什么影响,而new的方式随着实例的对象越来越多,性能会急剧下降。

开发中的应用场景

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

在spring中bean的创建实际就是两种:单例模式和原型模式。在此看出原型模式一般是和工厂模式搭配起来一起使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值