《设计模式》6.原型模式(创建型)

作用:通过拷贝一个对象实例来创建一个新的对象。

Java 提供了 clone() 方法用于实现对象的拷贝,clone() 方法来自于 Object,Java 中所有的类都直接或间接继承自 Object,因此重写 clone() 方法即可实现对象的拷贝。

  • 重写的 clone() 方法中,依次调用父类的 clone() 方法,最终调用 Object.clone() 方法,通知 JVM 实现对象的拷贝
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;

@Data
public class Order {
    private String orderNo;
    private List<Product> products;
    private String customer;
    private String contactNumber;
    private String shippingAddress;
    private BigDecimal amount;

    public BigDecimal getAmount() {
        if (products == null || products.size() == 0) {
            return BigDecimal.ZERO;
        }

        BigDecimal totalAmount = new BigDecimal(0);
        for (Product prod : products) {
            totalAmount = totalAmount.add(prod.getPrice());
        }
        return totalAmount;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //调用 Object.clone();
        return super.clone();
    }
}
import lombok.Data;
import java.math.BigDecimal;

@Data
public class Product {
    private String productNo;
    private BigDecimal price;
}
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class OrderTester {
    public static void main(String args[]) {
        Product product1 = new Product();
        product1.setProductNo("P0001");
        product1.setPrice(new BigDecimal(2));

        List<Product> products = new ArrayList<>(2);
        products.add(product1);

        Order order = new Order();
        order.setOrderNo("O201904170001");
        order.setCustomer("ZhangSan");
        order.setProducts(products);
        order.setContactNumber("130 xxxx xxxx");
        order.setShippingAddress("xx省-xx市-xx区-xx小区");
        System.out.println("Order: \n" + order);

        Order newOrder = null;
        try {
            if ((newOrder = (Order) order.clone()) == null) {
                System.err.println("\nOrder clone failed!");
            }
            System.out.println("\nCopy Order: \n" + newOrder);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

当执行测试类时,程序抛出了异常:

java.lang.CloneNotSupportedException: com.designpatterns.prototype.complex.Order
	at java.lang.Object.clone(Native Method)
	at com.designpatterns.prototype.complex.Order.clone(Order.java:31)
	at com.designpatterns.prototype.complex.OrderTester.main(OrderTester.java:26)

查看 JDK 源码发现,clone 方法是一个 native 方法,重写 clone() 方法的同时,类必须实现 Cloneable 接口,用来告知 JVM 该对象可以拷贝,Cloneable 接口未定义任何方法,仅作为一种标识存在:

Object.clone()

	/*
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. 

     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.

     * @throws  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
     * @see java.lang.Cloneable
     */
    protected native Object clone() throws CloneNotSupportedException;

Cloneable 接口

/**
 * A class implements the <code>Cloneable</code> interface to
 * indicate to the {@link java.lang.Object#clone()} method that it
 * is legal for that method to make a
 * field-for-field copy of instances of that class.
 * <p>
 * Invoking Object's clone method on an instance that does not implement the
 * <code>Cloneable</code> interface results in the exception
 * <code>CloneNotSupportedException</code> being thrown.
 * <p>
 * By convention, classes that implement this interface should override
 * <tt>Object.clone</tt> (which is protected) with a public method.
 * See {@link java.lang.Object#clone()} for details on overriding this
 * method.
 * <p>
 * Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
 * Therefore, it is not possible to clone an object merely by virtue of the
 * fact that it implements this interface.  Even if the clone method is invoked
 * reflectively, there is no guarantee that it will succeed.
 *
 * @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

Order 实现 Cloneable 接口后,执行测试类,对象拷贝成功

public class Order implements Cloneable {
	...
}

执行结果

Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Copy Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

修改测试类,添加存款属性:deposit

import java.util.HashMap;
import java.util.Map;

public class OrderTester {
    public static void main(String args[]) {
        Product product1 = new Product();
        product1.setProductNo("P0001");
        product1.setPrice(new BigDecimal(2));

        List<Product> products = new ArrayList<>(2);
        products.add(product1);

        Order order = new Order();
        order.setOrderNo("O201904170001");
        order.setCustomer("ZhangSan");
        order.setProducts(products);
        order.setContactNumber("130 xxxx xxxx");
        order.setShippingAddress("xx省-xx市-xx区-xx小区");
        System.out.println("Order: \n" + order);

        Order newOrder = null;
        try {
            if ((newOrder = (Order) order.clone()) == null) {
                System.err.println("\nOrder clone failed!");
            }
            System.out.println("\nCopy Order: \n" + newOrder);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        product1.setPrice(new BigDecimal(100));
        System.out.println("\nUpdate product #1 price for Order:\n" + order);
        System.out.println("\nNew Order's product #1 price is updated at same time:\n" + newOrder);
    }
}

通过运行结果发现,对象 clone 之后,更新原始对象 Product #1 的 price 属性值后,新对象的 Product #1 的 price 属性也相应发生了改变,这是因为 Java 引用对象保存的是引用的地址,即堆内存中分配的地址,这里就涉及到 浅拷贝深拷贝

  • 浅拷贝:仅拷贝自身对象和值类型的成员变量,对引用类型的成员变量只拷贝其引用
  • 深拷贝:拷贝自身对象、值类型的成员变量、引用类型的成员变量,实现方式有两种:
    • 涉及相关 引用类型 实现 Cloneable 接口,重写 clone 方法,调用 super.clone(),(String、BigDecimal 类型除外;String 因为线程池的设计,replace、substring 等方法会生成新的 String 对象;BigDecimal 运算也会生成新的对象)
    • 通过 序列化 与 反序列化 方式实现,需要通过 字节数组 或 文件 的方式作为临时缓存

注:这里 String 虽然也是 引用类型,因为 字符串常量池的设计,改变原始对象的属性将不会对新对象造成影响。

Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Copy Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=100)

New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

深拷贝

  1. Product 实现 Cloneable 接口并重写 clone 方法
import lombok.Data;
import java.math.BigDecimal;

@Data
public class Product implements Cloneable {
    private String productNo;
    private BigDecimal price;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. 更新 Order.clone() 方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Order newOrder = null;
        try {
            newOrder = (Order) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("Order clone failed!");
        }

        List<Product> newProducts = new ArrayList<>(products.size());
        for (Product product : products) {
            newProducts.add((Product) product.clone());
        }
        newOrder.setProducts(newProducts);
        return newOrder;
    }

再次执行测试类,新对象中 Product #1 的 price 属性并未发生改变,原始对象与新对象完全独立,互不影响。

Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Copy Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=100)

New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

通过 序列化 & 反序列化 的方式实现深拷贝,必须实现 Serializable 接口(生成 serialVersionUID)

  • 默认情况下,被 trainsient 关键字修饰的成员变量将不会被序列化
public class Order implements Cloneable, Serializable {
    private static final long serialVersionUID = 3631376205708889405L;
	...
}
public class Product implements Cloneable, Serializable {
    private static final long serialVersionUID = -1207754602611415304L;
    ...
}
import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class OrderTester {
    public static void main(String args[]) {
        Product product1 = new Product();
        product1.setProductNo("P0001");
        product1.setPrice(new BigDecimal(2));

        List<Product> products = new ArrayList<>(2);
        products.add(product1);

        Order order = new Order();
        order.setOrderNo("O201904170001");
        order.setCustomer("ZhangSan");
        order.setProducts(products);
        order.setContactNumber("130 xxxx xxxx");
        order.setShippingAddress("xx省-xx市-xx区-xx小区");
        System.out.println("Order: \n" + order);

        byte[] bytes = writeObject(order);
        Order newOrder = readObject(bytes);
        if (newOrder == null) {
            System.err.println("\nOrder clone failed!");
            return;
        }
        System.out.println("\nCopy Order: \n" + newOrder);

        product1.setPrice(new BigDecimal(50));
        System.out.println("\nUpdate product #1 price for Order:\n" + order);
        System.out.println("\nNew Order's product #1 price is updated at same time:\n" + newOrder);
    }

    public static Order readObject(byte[] bytes) {
        Order newOrder = null;
        try (ByteArrayInputStream byteIS = new ByteArrayInputStream(bytes);
             ObjectInputStream input = new ObjectInputStream(byteIS)){

            newOrder = (Order) input.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return newOrder;
    }

    public static byte[] writeObject(Order order) {
        byte[] bytes = null;
        try (ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
             ObjectOutputStream output = new ObjectOutputStream(byteOS)) {

            output.writeObject(order);
            bytes = byteOS.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
}
Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Disconnected from the target VM, address: '127.0.0.1:58009', transport: 'socket'

Copy Order: 
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=50)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=50)

New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)

参考文章:
http://c.biancheng.net/view/1343.html
https://www.cnblogs.com/shakinghead/p/7651502.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值