开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情
概念
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
使用原型模式的步骤
- 目标类实现Cloneable接口 (标志接口)
- 重写接口的clone方法,将访问修饰符修改为public。
特定场景代码演示
-
问题
- OA系统中,有一个员工提交周报的功能,每个周五,总结本周工作内容,写出下周工作计划。
-
反例
- 代码
public class WeekReport { //自增主键记录 private static int incrementId = 0; //唯一标识 private int id; //部门 private String emp; //总结 private String summary; //下周计划 private String plain; //意见 private String suggestion; //提交时间 private Date time; public WeekReport(String emp, String summary, String plain, String suggestion, Date time) { this.id = ++incrementId; this.emp = emp; this.summary = summary; this.plain = plain; this.suggestion = suggestion; this.time = time; } } public class Test { public static void main(String[] args) { //第一周周报 WeekReport weekReport = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date()); //第二周周报 WeekReport weekReport2 = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date()); } }-
分析
大部分内容均相同,但是每周填写均需填写相同的部分。降低了工作效率。
-
正例(原型模式)
-
代码
public class WeekReport implements Cloneable { //自增主键记录 private static int incrementId = 0; //唯一标识 private int id; //部门 private String emp; //总结 private String summary; //下周计划 private String plain; //意见 private String suggestion; //提交时间 private Date time; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public WeekReport(String emp, String summary, String plain, String suggestion, Date time) { this.id = ++incrementId; this.emp = emp; this.summary = summary; this.plain = plain; this.suggestion = suggestion; this.time = time; } } public class Test { public static void main(String[] args) { //第一周周报 WeekReport weekReport = new WeekReport("杜少雄", "学习了程序的七大设计原则", "学习完设计模式", "无", new Date()); //第二周周报 WeekReport weekReport2 = (WeekReport) weekReport1.clone(); } } -
分析
-
实现Cloneable接口 重写clone方法
-
下一周写周报的时候,直接克隆(浅拷贝)上一周的周报实例,修改需要修改的部分即可。
-
存在的问题(浅拷贝的共享引用对象问题)
-
浅拷贝是直接复制二进制数据,其中的引用会也复制地址给副本,此时,源对象和克隆的对象是引用同一个对象。
-
解决方案1
@Override public WeekReport clone() throws CloneNotSupportedException { //通过周报1克隆周报2 WeekReport clone = (WeekReport) super.clone(); //通过周报2 克隆 data(指向周报2中的日期对象) Object data = clone.getTime().clone(); // 将data赋新值 data = new Date(); return clone; } -
解决方案2 使用序列化
@Override public WeekReport clone() throws CloneNotSupportedException { //将对象写入内存 ByteOutputStream byteOutputStream = new ByteOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream); objectOutputStream.writeObject(this); //从内存读取数据 byte[] bytes = byteOutputStream.toByteArray(); //将字节数作为input InputStream inputStream = new ByteArrayInputStream(bytes); //读取数据 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); return (WeekReport)objectInputStream.readObject(); }
-
-
-
-
最终的解决方案(原型模式)
@Override public WeekReport clone() throws CloneNotSupportedException { try { //将对象写入内存 ByteOutputStream byteOutputStream = new ByteOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOutputStream); objectOutputStream.writeObject(this); //从内存读取数据到字节数组 byte[] bytes = byteOutputStream.toByteArray(); //将字节数组作为input InputStream inputStream = new ByteArrayInputStream(bytes); //读取数据 ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); return (WeekReport)objectInputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; }
优缺点
原型模式的优点:
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。