0x1、定义与使用场景
又称 克隆模式
,定义如下:
如果对象的 创建成本较大,且同一个类的不同对象间差别不大(大部分字段相同)的情况下,可以利用已有对象(原型)进行复制(或者叫拷贝、克隆)的方式来创建新的对象,以达到节省创建时间的目的。
简单点说就是以某个对象为原型,克隆出一个一模一样的对象,常见应用场景:
- 资源优化 (对象初始化需使用较多外部资源时,如IO、数据库、CPU,权限等);
- 复杂依赖 (依赖嵌套,A创建依赖B,B创建依赖C,一连串的对象get和set);
- 同一对象多个修改者 (多个对象的调用者都需要修改对象的值,克隆多个供使用其使用,保护性拷贝);
- 需保存原始对象状态 (如记录历史操作的场景,通过原型模式快速保存记录);
- 结合工厂模式使用 (定义统一的复制接口,如clone、copy,使用一个工厂来统一进行拷贝和新对象创建,然后由工厂方法提供给调用者使用)。
组成角色:
网络异常,图片无法展示
|
0x2、深浅拷贝
原型模式分为 浅拷贝
和 深拷贝
两种,浅拷贝只复制对象中基本数据类型和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象...而深拷贝得到的则是一份完完整整独立的对象,所以,深拷贝更耗时和耗内存空间。
注:浅拷贝得到的对象和原始对象会共享部分数据,可能出现数据被修改的风险,故更适用于不可变对象。
很多开源框架和组件中都有原型模式的相关实现,并不一定非得从零去实现浅拷贝和深拷贝。下面以Java为例,讲解其中深浅拷贝相关的姿势~
① Java中==与equals的区别:
==
:基本数据类型(int,long等) → 比较存储的值是否相等,引用型变量 → 比较所指向对象地址是否相等;
- equals:不能用于比较基本数据类型,没有对equals()方法进行重写,默认比较指向的对象地址,若想比较对象内容,需自行重写此方法,做相应的判断。
② 克隆必须满足的三个条件
- 对任何对象x,都有 x.clone() != x,即不是同一对象;
- 对任何对象x,都有 x.clone().getClass == x.getClass(),即对象类型一致;
- 如果对象obj的equals()方法定义得当的话,obj.clone().equals(obj)应该是成立的(推荐,不强制);
③ Java中浅拷贝的实现方式
想被克隆的类实现 Cloneable
接口,重写 clone()
方法,验证代码示例如下(省略get、set方法):
// 引用类型 public class Money { private String type; // 币种 public Money(String type) { this.type = type; } @Override public String toString() { return "Money{" + "type='" + type + '\'' + '}'; } } // 原型类,实现Cloneable接口,重写clone方法 public class Assets implements Cloneable{ private int amount; // 数目 private Money money; // 币种 private String kind; // 资金种类 public Assets(int amount, Money money, String kind) { System.out.println("执行了构造方法!"); this.amount = amount; this.money = money; this.kind = kind; } @Override protected Object clone() { Assets assets = null; try { assets = (Assets)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return assets; } @Override public String toString() { return "Assets{" + "amount=" + amount + ", money=" + money + ", kind='" + kind + '\'' + '}'; } } // 测试用例: public class AssetsTest { public static void main(String[] args) { Assets a1 = new Assets(100, new Money("人民币"), "现金"); Assets a2 = (Assets) a1.clone(); System.out.println("a1.equals(a2) → " + a1.equals(a2)); System.out.println("a1 == a2 → " + (a1 == a2)); System.out.println("a1.getClass == a2.getClass → " + (a1.getClass() == a2.getClass())); System.out.println("a1.getMoney() == a2.getMoney() → " + (a1.getMoney() == a2.getMoney())); System.out.println("a1 → " + a1.toString()); System.out.println("a2 → " + a2.toString()); a1.setAmount(200); System.out.println("修改基本类型属性后:"); System.out.println("a1 → " + a1.toString()); System.out.println("a2 → " + a2.toString()); a1.getMoney().setType("美金"); System.out.println("修改引用用类型属性后:"); System.out.println("a1 → " + a1.toString()); System.out.println("a2 → " + a2.toString()); } }
运行结果如下:
网络异常,图片无法展示
|
分析运行结果不难得出Java中浅拷贝具有如下特点:
- 执行clone方法,不会调用构造方法;
- 克隆会生成新的对象变量,但指向同一个内存地址;
- 克隆前后数据类型一致;
- 克隆时,基本数据类型属性会新建,引用类型只会生成一个新的引用变量,依旧指向同一个内存地址;
④ Java中深拷贝的两种实现方式
相比浅拷贝,深拷贝会连引用类型数据也新建,Java中实现深拷贝的两种方式:
- 引用类型也实现Cloneable接口,重写clone()方法,原型类clone()方法调用引用类类型的clone()为money对象赋值:
@Override protected Object clone() { Assets assets = null; try { assets = (Assets)super.clone(); assets.setMoney((Money)this.getMoney().clone()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return assets; }
注:深拷贝需要整个克隆涉及的对象都正确的实现clone()方法,其只能怪一个没正确实现克隆,都会导致深拷贝失败。
- 序列化,定义一个方法完成对象转二进制流和二进制流转对象,然后返回反序列化后的对象。
public Assets deepClone() { try { // 写入当前对象的二进制流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 读取二进制流产生新对象 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (Assets)ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return null; }
0x3、加餐:四类创建型设计模式总结
单例模式
→ 创建全局唯一的对象;
工厂模式
→ 创建不同但类型相关的对象(继承同一父类或接口),由给点参数决定构建那种类型的对象;
建造者模式
→ 创建复杂对象,通过设置不同的可选参数,定制化创建不同对象;
原型模式
→ 针对创建成本较大的对象,利用已有对象进行复制的方式创建,以此达到节省创建时间的目的;