在《生化危机》系列电影中,克隆人是个频频出现的话题。保护伞公司为了需求复制出另一个战力相当的战士Alice,不惜克隆成百上千个Alice,然而直到最后,非但没有真正克隆出另一个完美的Alice,就连Alice自己也被证实是保护伞董事的一个克隆人。

克隆人一直是饱含伦理争议的话题,但是在软件设计领域,克隆思想却很受欢迎。软件旨在用程序替代人工去做重复性的工作。归根结底,“懒”是推动软件发展的重要助力,原型模式就是克隆思想的重要应用之一。


一言

原型实例指定创建对象的种类,通过拷贝这些原型创建新的对象。


概述

原型模式是一种创建型设计模式,它允许一个对象再创建一个可定制的对象而无需知道如何创建的细节。就像爱丽丝直到最后才指导自己也是克隆人一样,实例本身是不关注
创建实例的细节的。


原理

通过将一个原型对象传给要发动创建的对象,要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即bean.clone()。


欢迎来到保护伞公司

职员7568你好,欢迎你加入保护伞公司,徐江博士交给你的第一个任务是,编写代码,复制十个爱丽丝,加油!

循规蹈矩的豪赌

小职员7568稍加思索便写出了如下代码:

反例代码

Alice原型

public class Alice{private String name;private int age;private String skill;public Sheep(String name, int age, String skill) {this.name = name;this.age = age;this.skill= skill;}//setter & getter@Overridepublic String toString() {return "Alice{" +"name='" + name + '\'' +", age=" + age +", skill='" + skill+ '\'' +'}';}

驱动机

public class Client {public static void main(String[] args) {Alice alice= new Alice ("alice", 18, "极强的战斗天赋");Alice alice1= new Alice ("alice", 18, "极强的战斗天赋");Alice alice2= new Alice ("alice", 18, "极强的战斗天赋");Alice alice3= new Alice ("alice", 18, "极强的战斗天赋");Alice alice4= new Alice ("alice", 18, "极强的战斗天赋");System.out.println(alice);System.out.println(alice1);System.out.println(alice2);System.out.println(alice3);System.out.println(alice4);}}

不一会,职员7568就高呼自己完成了任务。
徐江博士看了看代码,吩咐手下,“拖出去喂丧尸,什么档次跟我在一个公司工作?”


所以,保护伞公司职员-1。

问题分析

那么究竟是什么问题导致博士那么生气呢?
首先,要肯定上述代码的优势,简单易操作。但是,在创建新对象的时候总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率会及低。而且每一次都需要重新初始化对象,灵活性太低。

浅拷贝

“老,老板,我有一个不太成熟的思路,不知道当讲不当讲”,职员7569瑟瑟发抖。
“ 讲!”
“Java中Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口cloneable该接口表示该类能够复制且具有复制的能力。通过这种方式,或许…”
徐博士听着这段构想,嘴角露出了不易察觉的微笑:
“很好,但是我有一个要求,我要让这些爱丽丝们做朋友”

代码实现

public class Alice implements Cloneable{private String name;private int age;private String skill;public Alice friend;public Alice (String name, int age, String skill) {this.name = name;this.age = age;this.skill= skill;}@Overrideprotected Object clone(){Alice alice = null;try {alice = (Alice ) super.clone();}catch (Exception e){System.err.println(e.getMessage());}return alice ;} @Overridepublic String toString() {return "Alice{" +"name='" + name + '\'' +", age=" + age +", skill='" + skill+ '\'' +'}';}}

徐博士的检查

徐博士看了看代码,继续吩咐手下,“拖出去,喂丧尸,什么档次跟我待一个公司。”
职员7569高呼:“我不服,我的代码有什么问题?”
徐博士看了看他涨红的面庞,缓缓的将Alice实例的friend属性的哈希值打印了出来,小职员呆住了,再没有了一丝力气。


保护伞公司职员-1

在实体属性发生变更时,原型模式可以以很小的改动代价实现对象的拷贝。但是,上述实现的克隆并不会将本体的对象属性做到真正意义的克隆,只是将对象的引用复制了多份,所以我们称之为浅克隆。

深拷贝

职员7570汗流浃背了,因为下一个就轮到他来解决这个问题,他拼命的思考,终于,他起身了。
“博士,我想到了。我们复制对象的所有基本数据类型的成员变量值,为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。”
徐博士抬头看了看这个少年:“继续”
小职员咽了咽口水:“对于深拷贝的实现方式,我想可以通过重写clone方法…或者 ,或者通过对象实例化实现深拷贝。”
徐博士又深深的看了一言面前这个侃侃而谈的少年:“看来你不是个草包,写写看看吧。”

代码实现

public class DeepCloneAbleTarget implements Serializable,Cloneable{private static final long serivalVersionID = 1L;private String cloneName;private String cloneClass;public DeepCloneAbleTarget(String cloneName, String cloneClass) {this.cloneName = cloneName;this.cloneClass = cloneClass;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
public class DeepProtoType implements Serializable,Cloneable {public String name;public DeepCloneAbleTarget deepCloneAbleTarget;@Overrideprotected Object clone() throws CloneNotSupportedException {Object deep = null;deep =super.clone();DeepProtoType deepProtoType=(DeepProtoType) deep;deepProtoType.deepCloneAbleTarget = (DeepCloneAbleTarget) deepCloneAbleTarget.clone();return deepProtoType;}public Object deepClone(){ByteArrayOutputStream bos = null;ObjectOutputStream oos = null;ByteArrayInputStream bis = null;ObjectInputStream ois = null;try {bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);oos.writeObject(this);bis = new ByteArrayInputStream(bos.toByteArray());ois = new ObjectInputStream(bis);DeepProtoType copyObj = (DeepProtoType) ois.readObject();return copyObj;}catch (Exception e){System.err.println(e.getMessage());return null;}finally {try {bos.close();oos.close();bis.close();ois.close();} catch (IOException e) {e.printStackTrace();}}}}

创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。不用重新初始化对象,而是动态地获得对象运行时的状态。如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化无需修改代码。

但是在实现深克隆的时候可能需要比较复杂的代码。同时,需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了ocp原则。

好啦,今天在保护伞公司的打工之旅就到此为止,祝各位好运。


关注我,共同进步,每周至少一更。——Wayne