Java设计模式之原型模式(Prototype)—— 浅拷贝和深拷贝

1. 原型模式

1.1 定义

原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。(引自《大话设计模式》)

1.2 使用场景

这里举例说明一下。比如说现在我要复制一本书。首先,我定义了这本书的相关属性,有书名、价格、作者(一本书可以有多个作者)。

package main.mode.yxms;

import java.util.ArrayList;

public class Book {

	private String title;//书名
	private int price;//价格
	private ArrayList<String> authors = new ArrayList<String>();
	
    public void show() {
    	System.out.println("title:"+title);
    	String s = "";
    	for (String author : authors) {
			s = s.concat(author);
			s = s.concat("、");
		}
    	System.out.println("authors:"+s.substring(0,s.length()-1));
    	System.out.println("price:"+price);
    	System.out.println("----------华丽丽的分割线----------");
    }
    
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}

	public ArrayList<String> getAuthors() {
		return authors;
	}

	public void addAuthors(String author) {
		this.authors.add(author);
	}
}

这里注意的是,为了方便list赋值,我将list的set方法改为add方法。

好了,我现在要复制书了,复制之前我当然要先创建一本书,然后进行复制,现在我能想到两种复制方式,一是new,二是=。先说new。

package main.mode.yxms;

public class Test {

	public static void main(String[] args) {
		//创建一本书
		Book book1 = new Book();
		book1.setTitle("书名1");
		book1.setPrice(10);
		book1.addAuthors("作者1");
		book1.addAuthors("作者2");
		book1.show();
		
		//复制一本书
		Book book2 = new Book();
		book2.setTitle("书名1");
		book2.setPrice(10);
		book2.addAuthors("作者1");
		book2.addAuthors("作者2");
		book2.show();
	}

}

结果如下

可以看出,复制的book2和book1一模一样。

再来说一下第二种方式,=。

package main.mode.yxms;

public class Test {

	public static void main(String[] args) {
		//创建一本书
		Book book1 = new Book();
		book1.setTitle("书名1");
		book1.setPrice(10);
		book1.addAuthors("作者1");
		book1.addAuthors("作者2");
		book1.show();
		
		//复制一本书
		Book book2 = new Book();
		book2 = book1;
		book2.show();
	}

}

结果同上

区别在哪呢,当采用方式二时,我们只是复制了book1的引用,如果我们改变book1,book2也会随之改变。

package main.mode.yxms;

public class Test {

	public static void main(String[] args) {
		//创建一本书
		Book book1 = new Book();
		book1.setTitle("书名1");
		book1.setPrice(10);
		book1.addAuthors("作者1");
		book1.addAuthors("作者2");
		book1.show();
		
		//复制一本书
		Book book2 = new Book();
		book2 = book1;
		book2.show();
		
		book1.setPrice(110);
		book1.show();
		
		book2.show();
	}

}

结果变为

这里多说一句,如果是book2修改了内容(如book1.setPrice(110);改为book2.setPrice(110);结果同上)

现在来说说这两种方式的缺点:

方式一,比较麻烦,如果book的属性特别多,我们在复制的时候也要写很多的set方法,代码冗余。好处是book2“复制”的是实际内容,book1改变内容时,book2不受影响。

方式二,代码简便,但复制的不是实际内容而是引用。这就相当于book2其实是一本空书,里面都是白页,如果想看内容,它告诉要去book1看一样。

如此看来,我们可以有一个更好的办法去实现“复制”这项功能。就是本文所说的模型模式。

使用场景:

(1)当源对象属性较多时。

(2)需要多个拷贝对象,各个拷贝对象需要修改部分值时,可以保障源对象的值不变。

1.3 原型模式类图

Prototype:原型类,声明一个克隆自身的接口。在Java中相当于Cloneable接口。

ConcretePrototype:具体的原型类,实现一个克隆自身的方法。

Client:客户端调用,让一个原型克隆自身从而创建一个新的对象。

这里简单说下Cloneable接口。模型模式的核心就是clone()方法。大家应该知道这是Object的方法,如果打开Object源码查看,可以看到clone()方法定义,

protected native Object clone() throws CloneNotSupportedException;

Object类是所有类的父类,即每个类都默认继承了Object类。既然如此,为什么这里还要用Cloneable呢?Cloneable是Java提供的一个接口,用来标示这个对象是可以拷贝的。查看Cloneable的源码,可以看到这个接口中一个方法都没有,它只是起到一个标记作用。在jvm中只有这个标记的对象才有可能被拷贝。只是有可能,一个对象要是可以被拷贝,需要重写clone()方法。即

@Override
public Book clone()  {
        ...... 
}

这里强调一点,对象拷贝时,类的构造函数是不会被执行的。

举个例子,一目了然。

public class ConstructorTest implements Cloneable{
	
	public ConstructorTest() {
		System.out.println("进来构造函数了~~~");
	}

	public static void main(String[] args) {
		ConstructorTest c = new ConstructorTest();
		try {
			c.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}

}

结果

可以看出,构造函数只被执行了一次,应该是new的时候执行的,clone的时候并没有被执行。

从底层原理来说,Object类的clone方法是从内存中(具体的说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数自然就不会被执行了。(构造函数既然是方法,应该是存在方法区中的。)

2. 实战应用

2.1 举个栗子

就上面那个例子,我要复制一本书。可以用Object中的clone()方法进行拷贝。这里对Book类进行简单修改,增加clone方法。

package main.mode.yxms;

import java.util.ArrayList;

public class Book implements Cloneable {

	private String title;//书名
	private ArrayList<String> authors = new ArrayList<String>();//作者
	private int price;//价格
	
	/**
     * 重写拷贝方法
     */
	@Override
    protected Book clone()  {
        try {
            Book book = (Book) super.clone();
            return book;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public void show() {
    	System.out.println("title:"+title);
    	String s = "";
    	for (String author : authors) {
			s = s.concat(author);
			s = s.concat("、");
		}
    	System.out.println("authors:"+s.substring(0,s.length()-1));
    	System.out.println("price:"+price);
    	System.out.println("----------华丽丽的分割线----------");
    	
    }
    
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public ArrayList<String> getAuthors() {
		return authors;
	}
	public void addAuthors(String author) {
		this.authors.add(author);
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
}

客户端测试

package main.mode.yxms;

public class PrototypeTest {

	public static void main(String[] args) {
		//创建一本书
		Book book1 = new Book();
		book1.setTitle("书名1");
		book1.addAuthors("作者1");
		book1.setPrice(10);
		book1.show();
		
		//复印一本书
		Book book2 = new Book();
		book2 = book1.clone();
		book2.show();
		
		//修改book2
		book2.setTitle("书名2");
		book2.setPrice(12);
		book2.addAuthors("作者2");
		book2.show();
		
		//此时book1
		book1.show();
	}

}

结果

从结果可以看出,采用clone方法,book2可以完全复制book1的全部内容。当修改book2的内容时,我们发现,title和price更改不会改变book1的内容,但authors的更改却改变了book1的内容。这又为什么呢?

这里涉及一个新的问题——浅拷贝与深拷贝。

2.2 浅拷贝

Object类提供的clone方法,只是拷贝对象的基本的数据类型(也包括String),对于对象内部的数组、集合、引用对象等都不拷贝,而是指向原生对象内部元素的地址,这种拷贝就叫做浅拷贝。例如上面的例子。

如上图所示,book2在拷贝book1时,将title和price的值也拷贝了,但authors只是拷贝了引用地址,指向的内容区域和book1实际上是统一区域,此时如果book1或book2任一方改变了值,另一方也就变了。

这就是浅拷贝。如何改善呢?就是采用深拷贝。

2.3 深拷贝

代码做如下修改

package main.mode.yxms;

import java.util.ArrayList;

public class Book implements Cloneable {

	private String title;//书名
	private ArrayList<String> authors = new ArrayList<String>();//作者
	private int price;//价格
	
	/**
     * 重写拷贝方法
     */
	@Override
    protected Book clone()  {
        try {
            Book book = (Book) super.clone();
            // 手动对authors对象也调用clone()函数,进行拷贝
            book.authors =  (ArrayList<String>) this.authors.clone();
            return book;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public void show() {
    	System.out.println("title:"+title);
    	String s = "";
    	for (String author : authors) {
			s = s.concat(author);
			s = s.concat("、");
		}
    	System.out.println("authors:"+s.substring(0,s.length()-1));
    	System.out.println("price:"+price);
    	System.out.println("----------华丽丽的分割线----------");
    	
    }
    
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public ArrayList<String> getAuthors() {
		return authors;
	}
	public void addAuthors(String author) {
		this.authors.add(author);
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
}

客户端测试(同上面的测试类)

package main.mode.yxms;

public class PrototypeTest {

	public static void main(String[] args) {
		//创建一本书
		Book book1 = new Book();
		book1.setTitle("书名1");
		book1.addAuthors("作者1");
		book1.setPrice(10);
		book1.show();
		
		//复印一本书
		Book book2 = new Book();
		book2 = book1.clone();
		book2.show();
		
		//修改book2
		book2.setTitle("书名2");
		book2.setPrice(12);
		book2.addAuthors("作者2");
		book2.show();
		
		//此时book1并不受影响
		book1.show();
	}

}

结果

可以看出,book1不受影响,这就是深拷贝。

3. 总结

优点:

(1)可以保障原生对象的完整性,即初始化信息不发生改变。

(2)隐藏了对象创建的细节。

(3)大多数情况下,提高了性能。

缺点:

(1)当原生对象的属性较少时,new效率可能会更高。需要考虑实际业务情况使用原型模式。

(2)构造函数不会被拷贝。

最后一句话总结,原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。

 

说在后面:

本文主要是小猫看《大话设计模式》的笔记式的记录,方便以后查阅。

当然了,我也阅读了许多其它博友相关的博文,再加之自己的理解整理出本文,仅供参考。

 

参考:

《大话设计模式》

设计模式之原型模式 (这个博友写的很棒,大家可以看看)

23中设计模式之_原型模式(深/浅拷贝)  (这个博友写的也不错哦,大家参考参考)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值