原型模式(ProtoType) - Java里的对象复制


一, 引用的复制和对象复制.

在编程中, 我们有时会用两个引用指向同一个对象.


例如:

ArrayList a = new ArrayLIst();
ArrayList b = a;

看起来好像有a,b两个容器, 实际上a,b是两个引用, 它们都指向同1个Object的内存地址.


而对象复制是指:

在内存里划分一块与指定对象相同内容的内存.


也就是说内存里原理有1个Object的内存, 复制后就有两个了...




二, 对象复制的简便方法.

当然, 对象复制的实现十分简单, 只需要实例化1个具有相同属性的对象就ok了.

注意, 如果用 "==" 来讲比较两个引用是返回false, 因为它们的地址不同.


举个列子:

2.1 产品类Prod:

public class Prod {
	private int prodID;
	private String prodName;
	
	public int getProdID() {
		return prodID;
	}
	public void setProdID(int prodID) {
		this.prodID = prodID;
	}
	
	public String getProdName() {
		return prodName;
	}
	public void setProdName(String prodName) {
		this.prodName = prodName;
	}
	
	@Override
	public String toString(){
		return "Prod: " + this.getProdID() + " , " + this.getProdName();
	}
}


2.2 订单类Order:

public class Order {
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
}

2.3 客户端代码:

这里我先实例化1个order类 od1, 然后在复制1个od1 的对象od2

		Prod p1 = new Prod();
		p1.setProdID(1);
		p1.setProdName("Hammer");
		
		Order od1 = new Order();
		od1.setOrderID(1);
		od1.setProd(p1);
		od1.setAmount(20);
		
		Prod p2 = new Prod();
		p2.setProdID(1);
		p2.setProdName("Hammer");
		
		Order od2 = new Order();
		od2.setOrderID(1);
		od2.setProd(p1);
		od2.setAmount(20);
		
		System.out.println(od1);
		System.out.println(od2);


输出:

Order: 1 , 20 Prod: 1 , Hammer
Order: 1 , 20 Prod: 1 , Hammer



2.4 这种方法的缺点

首先这种写法不够优雅, 很多重复代码.


其次, 这种复制方法调用了类的构造函数.

就如上面的例子, 即使Order类的构造函数是空的, 但是实际上构造函数的执行机制很复杂,.

构造函数最重要的动作就是给类里的每个成员划分内存空间. 毕竟java是类c语言, 相当于执行了很多次malloc()函数.


如果某些业务类的构造函数写的很复杂, 就更耗费资源了.

而且



三, 利用Object.clone()方法来实现对象复制.


java里所有对象都是直接或简直继承自基类Object.

而clone()是基类的Native 方法, 所谓Native方法可以认为是java 内部特定方法, 它比非Native的执行效率要好.



也就是说, 利用Object.clone() 方法会比实例化1个相同内容对象效率要好.


3.1 Object.clone()方法和 Cloneable接口:

Object.clone() 方法有点特殊,  首先它是public方法, 也就是说它可以通过引用名和"."来调用.

但是它不能被子类继承, 也就说, 出了Object类外所有java里的class里面都没有clone()这个方法的.


如果要使用Object.clone()方法.

只能引用1个叫做 Cloneable里的Interface,  然后重写Cloneable接口里的clone()方法, 在里面调用object.clone().


这个接口就起了1个标记的作用, 帮组实现多态.



3.2 订单类Order:

我们按在这个思路来修改上面的订单类Order:

public class Order implements Cloneable{
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
	
	@Override
	public Object clone(){
		Object o = null;
		try{
			o = super.clone();
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return o;
	}
}

值得注意的是, Object.clone() 回抛异常.

而, Prod类没有任何修改.


3.3 客户端代码:

Prod p1 = new Prod();
		p1.setProdID(2);
		p1.setProdName("knife");
		
		Order od1 = new Order();
		od1.setOrderID(2);
		od1.setAmount(30);
		od1.setProd(p1);
		
		Order od2 = (Order)od1.clone();
		System.out.println(od1);
		System.out.println(od2);


可见, 只需要执行一句
Order od2 = (Order)od1.clone();

就相当于 复制了1个对象.


3.4 UML图

我们来看看这个例子的UML:


实际上这个就是原型模式(prototype)的UML图了,

原来我们不知不觉得使用了原型模式.



四, 原型模式的定义


原型模式(protoType), 用原型实例制定创建对象的种类, 并且通过copy这些原型创建新的对象.


总觉得设计模式的定义都太过于简单恶心了.


上面的

原型指的是  Cloneable这个接口.

原型实例指的是 Order这个类.



五, 浅复制和深复制

对象复制也分两种.


浅复制:

           两个对象中的值类型成员属性相等,  对象成员成员属性实际上没有复制,都是同一个对象.


深复制:

          两个对象中的值类型成员属性相等,  对象成员属性指向不同的 具有相同成员的 对象.



如上面的例子, 稍稍修改客户端代码:

		Prod p1 = new Prod();
		p1.setProdID(2);
		p1.setProdName("knife");
		
		Order od1 = new Order();
		od1.setOrderID(2);
		od1.setAmount(30);
		od1.setProd(p1);
		
		Order od2 = (Order)od1.clone();
		System.out.println(od1);
		System.out.println(od2);
		
		od1.setOrderID(3);
		od1.setAmount(40);
		od1.getProd().setProdName("blade");
		
		System.out.println(od1);
		System.out.println(od2);

上面 构建了对象od1,

然后复制出了另1个对象od2,


然后修改od1的 值成员 和 对象成员

然后输出od1, 和 od2 成员的值:

Order: 2 , 30 Prod: 2 , knife
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 2 , 30 Prod: 2 , blade


可见, 当od1 的id 和 amount的值被改成 3 和 40 后,  od2 的id和 Amount的值并不受影响, 因为它们的值类型成员属性是相互独立的.

但是当od1 的 prod属性的内容被修改后,    od2 的也被修改了(knife - > blade),  因为 od1 和 od2 的 prod成员指向的都是同1个对象.


这就证明了:

Java里的 Object.clone()方法实现的是浅复制.


六, 原型模式的深复制实现

接下来再次修改上面的例子, 令其实现成深复制

而且, 根据项目需要, 复制并不会复制Order的id, 也就是订单号码是唯一的, 只复制Prod和数量.


6.1 订单类Order:

public class Order implements Cloneable{
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
	
	@Override
	public Object clone(){
		Prod p = new Prod();
		p.setProdID(this.getProd().getProdID());
		p.setProdName(this.getProd().getProdName());
		
		Order o = new Order();
		o.setOrderID(this.getOrderID() + 1);
		o.setAmount(this.getAmount());
		o.setProd(p);
		
		return o;
	}
}

客户端输出:

Order: 2 , 30 Prod: 2 , knife
Order: 3 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 3 , 30 Prod: 2 , knife


可以, 无论od1修改了什么,  od2 都不受影响


这种方法放弃了Object.clone(), Order类在重写clone()方法里还是使用了构造方法去实例化1个新的对象.


这种方法配置更加灵活(选择性地复制成员)

当时放弃了Object.clone()的效能优点.

但是这仍然实现了原型模式(protoType)

























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nvd11

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

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

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

打赏作者

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

抵扣说明:

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

余额充值