设计模式之原型模式学习理解
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。 3、逃避构造函数的约束。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
下面是项目里一个报告模板的例子
简单的写法
1 /** 2 * 报告模板 3 * @author ko 4 * 5 */ 6 public class ReportTemplate { 7 private String title;// 标题 8 9 private String reportNum;// 报告编号 10 private Cover cover;// 封面 11 private DeviceInfo deviceInfo;// 设备信息 12 13 public ReportTemplate(String title) { 14 super(); 15 this.title = title; 16 } 17 18 // 设置数据 19 public void setData(String reportNum, Cover cover, DeviceInfo deviceInfo){ 20 this.reportNum = reportNum; 21 this.cover = cover; 22 this.deviceInfo = deviceInfo; 23 } 24 25 // 显示报告 26 public void display(){ 27 System.out.println("ReportTemplate [reportNum=" + reportNum + ",title="+title+", cover=" + cover + ", deviceInfo=" + deviceInfo + "]"); 28 } 29 30 }
1 /** 2 * 设备信息 3 * @author ko 4 * 5 */ 6 public class DeviceInfo { 7 8 private String deviceName;// 设备名 9 private String manufacturingDate;// 制造日期 10 private String maintainUnit;// 保养单位 11 private String preInspectDate;// 上次检验日期 12 public DeviceInfo(String deviceName, String manufacturingDate, String maintainUnit, String preInspectDate) { 13 super(); 14 this.deviceName = deviceName; 15 this.manufacturingDate = manufacturingDate; 16 this.maintainUnit = maintainUnit; 17 this.preInspectDate = preInspectDate; 18 } 19 @Override 20 public String toString() { 21 return "DeviceInfo [deviceName=" + deviceName + ", manufacturingDate=" + manufacturingDate + ", maintainUnit=" 22 + maintainUnit + ", preInspectDate=" + preInspectDate + "]"; 23 } 24 25 }
1 /** 2 * 测试类 3 * @author ko 4 * 5 */ 6 public class Test { 7 8 public static void main(String[] args) { 9 ReportTemplate reportTemplate = new ReportTemplate("电梯定期检验报告"); 10 reportTemplate.setData("YZ-TD(3110)-2015-0741", new Cover("宝应NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-06", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-02", "扬州一帆风顺电梯维保有限公司", "2015-07-07")); 11 12 ReportTemplate reportTemplate1 = new ReportTemplate("电梯定期检验报告"); 13 reportTemplate1.setData("YZ-TD(3110)-2015-0742", new Cover("高邮NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-07", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-07", "扬州两帆风顺电梯维保有限公司", "2015-07-17")); 14 15 reportTemplate.display(); 16 reportTemplate1.display(); 17 18 } 19 }
ReportTemplate [reportNum=YZ-TD(3110)-2015-0741title=电梯定期检验报告, cover=Cover [useUnit=宝应NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-06, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-02, maintainUnit=扬州一帆风顺电梯维保有限公司, preInspectDate=2015-07-07]] ReportTemplate [reportNum=YZ-TD(3110)-2015-0742title=电梯定期检验报告, cover=Cover [useUnit=高邮NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-07, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-07, maintainUnit=扬州两帆风顺电梯维保有限公司, preInspectDate=2015-07-17]]
同一种类型设备的报告,有几台设备就要实例化几次报告模板,这样会使得客户端代码变得很臃肿,另外万一某个地方写错一个字,就要改N次了。
利用原型模式进行该进代码,直接克隆
1 /** 2 * 报告模板 3 * @author ko 4 * 5 */ 6 public class ReportTemplate implements Cloneable { 7 private String title;// 标题 8 9 private String reportNum;// 报告编号 10 private Cover cover;// 封面 11 private DeviceInfo deviceInfo;// 设备信息 12 13 public ReportTemplate(String title) { 14 super(); 15 this.title = title; 16 } 17 18 // 设置数据 19 public void setData(String reportNum, Cover cover, DeviceInfo deviceInfo){ 20 this.reportNum = reportNum; 21 this.cover = cover; 22 this.deviceInfo = deviceInfo; 23 } 24 25 // 显示报告 26 public void display(){ 27 System.out.println("ReportTemplate [reportNum=" + reportNum + ",title="+title+", cover=" + cover + ", deviceInfo=" + deviceInfo + "]"); 28 } 29 30 @Override 31 protected Object clone() throws CloneNotSupportedException { 32 // TODO Auto-generated method stub 33 return super.clone(); 34 } 35 36 }
1 /** 2 * 测试类 3 * @author ko 4 * 5 */ 6 public class Test { 7 8 public static void main(String[] args) { 9 try { 10 ReportTemplate reportTemplate = new ReportTemplate("电梯定期检验报告"); 11 reportTemplate.setData("YZ-TD(3110)-2015-0741", new Cover("宝应NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-06", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-02", "扬州一帆风顺电梯维保有限公司", "2015-07-07")); 12 13 ReportTemplate reportTemplate1 = (ReportTemplate) reportTemplate.clone(); 14 reportTemplate1.setData("YZ-TD(3110)-2015-0742", new Cover("高邮NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-07", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-07", "扬州两帆风顺电梯维保有限公司", "2015-07-17")); 15 16 reportTemplate.display(); 17 reportTemplate1.display(); 18 } catch (CloneNotSupportedException e) { 19 // TODO Auto-generated catch block 20 e.printStackTrace(); 21 } 22 23 } 24 }
其它两个bean不变,红色代码部分就是新改的代码,某份报告要修改这样只要改报告的不同部分,不会对其它报告有影响,另外这样克隆的写法对性能也会提高,因为原来每new一次都要执行一次构造函数,如果构造函数很复杂那么执行时间就会很长,那N次的初始化操作就会变得很低效,一般在初始化的信息不发生变化的情况下,克隆是最好的办法,这既隐藏了对象创建的细节,又对性能是大大的提高。原型模式可以不用重新初始化对象,而是动态的获得对象运行时的状态。
浅复制与深复制
1 /** 2 * 报告模板 3 * @author ko 4 * 5 */ 6 public class ReportTemplate implements Cloneable { 7 private Title title;// 标题 8 9 private String reportNum;// 报告编号 10 private Cover cover;// 封面 11 private DeviceInfo deviceInfo;// 设备信息 12 13 public ReportTemplate() { 14 super(); 15 this.title = new Title(); 16 } 17 18 // 设置标题 19 public void setTitle(String masterTitle, String slaveTitle){ 20 title.setMasterTitle(masterTitle); 21 title.setSlaveTitle(slaveTitle); 22 } 23 24 // 设置数据 25 public void setData(String reportNum, Cover cover, DeviceInfo deviceInfo){ 26 this.reportNum = reportNum; 27 this.cover = cover; 28 this.deviceInfo = deviceInfo; 29 } 30 31 // 显示报告 32 public void display(){ 33 System.out.println("ReportTemplate [reportNum=" + reportNum + ",title="+title+", cover=" + cover + ", deviceInfo=" + deviceInfo + "]"); 34 } 35 36 @Override 37 protected Object clone() throws CloneNotSupportedException { 38 // TODO Auto-generated method stub 39 return super.clone(); 40 } 41 42 }
1 /** 2 * 标题类 3 * @author ko 4 * 5 */ 6 public class Title { 7 8 private String masterTitle;// 主标题 9 private String slaveTitle;// 副标题 10 11 public String getMasterTitle() { 12 return masterTitle; 13 } 14 15 16 public void setMasterTitle(String masterTitle) { 17 this.masterTitle = masterTitle; 18 } 19 20 21 public String getSlaveTitle() { 22 return slaveTitle; 23 } 24 25 26 public void setSlaveTitle(String slaveTitle) { 27 this.slaveTitle = slaveTitle; 28 } 29 30 31 @Override 32 public String toString() { 33 return masterTitle + "/" + slaveTitle; 34 } 35 }
1 /** 2 * 测试类 3 * @author ko 4 * 5 */ 6 public class Test { 7 8 public static void main(String[] args) { 9 try { 10 ReportTemplate reportTemplate = new ReportTemplate(); 11 reportTemplate.setTitle("电梯定期检验报告", "附带原始记录"); 12 reportTemplate.setData("YZ-TD(3110)-2015-0741", new Cover("宝应NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-06", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-02", "扬州一帆风顺电梯维保有限公司", "2015-07-07")); 13 14 ReportTemplate reportTemplate1 = (ReportTemplate) reportTemplate.clone(); 15 reportTemplate.setTitle("电梯监督检验报告", "不带原始记录"); 16 reportTemplate1.setData("YZ-TJ(3110)-2015-0742", new Cover("高邮NoVanke物业服务有限公司", "3110", "曳引式电梯", "2016-06-07", "江苏省特种设备安全监督检验研究院"), new DeviceInfo("曳引驱动乘客电梯", "2012-02-07", "扬州两帆风顺电梯维保有限公司", "2015-07-17")); 17 18 reportTemplate.display(); 19 reportTemplate1.display(); 20 } catch (CloneNotSupportedException e) { 21 // TODO Auto-generated catch block 22 e.printStackTrace(); 23 } 24 25 } 26 }
ReportTemplate [reportNum=YZ-TD(3110)-2015-0741,title=电梯监督检验报告/不带原始记录, cover=Cover [useUnit=宝应NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-06, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-02, maintainUnit=扬州一帆风顺电梯维保有限公司, preInspectDate=2015-07-07]] ReportTemplate [reportNum=YZ-TJ(3110)-2015-0742,title=电梯监督检验报告/不带原始记录, cover=Cover [useUnit=高邮NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-07, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-07, maintainUnit=扬州两帆风顺电梯维保有限公司, preInspectDate=2015-07-17]]
这次我把原来String类型的title换成了对象类型的title,它们的区别是值类型与引用类型的区别,对于clone()方法,如果字段是值类型的,则会对该字段进行逐位复制,如果字段是引用类型,则复制引用,但不复制引用的对象,因此原始对象及其复本引用同一对象。在这里看红色代码部分,明明两次set的title是不一样的,但是最后打印出来的却都是最后一次set的值,这就是因为这里的clone对于引用对象title,只是复制了引用,但其实它们是同一对象。
‘浅复制’,被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其它对象的引用都仍指向原来的对象。
‘深复制’,把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
实现报告模板深复制
1 /** 2 * 报告模板 3 * @author ko 4 * 5 */ 6 public class ReportTemplate implements Cloneable { 7 private Title title;// 标题 8 9 private String reportNum;// 报告编号 10 private Cover cover;// 封面 11 private DeviceInfo deviceInfo;// 设备信息 12 13 public ReportTemplate() { 14 super(); 15 this.title = new Title(); 16 } 17 18 // 私有的构造函数传入标题类,以便进行clone title 19 private ReportTemplate(Title title){ 20 try { 21 this.title = (Title) title.clone(); 22 } catch (CloneNotSupportedException e) { 23 // TODO Auto-generated catch block 24 e.printStackTrace(); 25 } 26 } 27 28 // 设置标题 29 public void setTitle(String masterTitle, String slaveTitle){ 30 title.setMasterTitle(masterTitle); 31 title.setSlaveTitle(slaveTitle); 32 } 33 34 // 设置数据 35 public void setData(String reportNum, Cover cover, DeviceInfo deviceInfo){ 36 this.reportNum = reportNum; 37 this.cover = cover; 38 this.deviceInfo = deviceInfo; 39 } 40 41 // 显示报告 42 public void display(){ 43 System.out.println("ReportTemplate [reportNum=" + reportNum + ",title="+title+", cover=" + cover + ", deviceInfo=" + deviceInfo + "]"); 44 } 45 46 /** 47 * 这里是重点,调用私有的构造函数,让“标题克隆完成”,然后再给这个模板的相关字段赋值,最终返回一个深复制的报告模板 48 */ 49 protected Object clone() { 50 ReportTemplate reportTemplate = new ReportTemplate(this.title); 51 reportTemplate.reportNum = this.reportNum; 52 reportTemplate.cover = this.cover; 53 reportTemplate.deviceInfo = this.deviceInfo; 54 return reportTemplate; 55 } 56 57 }
ReportTemplate [reportNum=YZ-TD(3110)-2015-0741,title=电梯监督检验报告/不带原始记录, cover=Cover [useUnit=宝应NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-06, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-02, maintainUnit=扬州一帆风顺电梯维保有限公司, preInspectDate=2015-07-07]] ReportTemplate [reportNum=YZ-TJ(3110)-2015-0742,title=电梯定期检验报告/附带原始记录, cover=Cover [useUnit=高邮NoVanke物业服务有限公司, deviceCode=3110, deviceType=曳引式电梯, inspectDate=2016-06-07, inspectionOrganization=江苏省特种设备安全监督检验研究院], deviceInfo=DeviceInfo [deviceName=曳引驱动乘客电梯, manufacturingDate=2012-02-07, maintainUnit=扬州两帆风顺电梯维保有限公司, preInspectDate=2015-07-17]]
比较重要的代码都用红色标出了,其它类不变,观察打印结果可以发现这次实现了报告模板的深复制。