阅读本文大概需要 3 分钟。
本篇是设计模式系列的第八篇,虽然之前也写过相应的文章,但是因为种种原因后来断掉了,而且发现之前写的内容也很渣,不够系统。
所以现在打算重写,加上距离现在也有一段时间了,也算是自己的一个回顾吧!
学而时习之,不亦说乎。
推荐阅读:
目前段位:青铜 I
Let's Go!
前言
设计模式不是语法,是一种巧妙的写法,能把程序变的更加灵活。架构模式比设计模式大,架构模式是战略,而设计模式是战术。
设计模式分为3大类型:创建型,行为型,结构型,总共有23种。
原型模式
原型模式(Prototype Pattern):它是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节。
业务需求
公司要对员工的工作经历进行录入,其余信息大致都差不多,如果靠手写则需要大量的时间,HR希望能有个类型的系统,来完成这份工作。
代码实现
接到需求,先对它进行分析。员工的其他信息大致差不多,只有工作经历不一样,其他人的档案我可以直接复制一开始这个员工的对象,在修改不相同的信息保存进行就可以了。
这样的方式貌似是设计模式种的原型模式,打开搜索引擎,去查查该模式的相关资料。
代码实现如下:
创建一个Resume类
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class Resume implements Cloneable{
6
7 private String name;
8 private String sex;
9 private String age;
10 private String timeArea;
11 private String company;
12
13 public Resume(String name) {
14 this.name = name;
15 }
16
17 /**
18 * 设置个人信息
19 *
20 * @param sex
21 * @param age
22 */
23 public void setPersonalInfo(String sex, String age) {
24 this.sex = sex;
25 this.age = age;
26 }
27
28 /**
29 * 设置工作经历
30 *
31 * @param timeArea
32 * @param company
33 */
34 public void setWorkExperience(String timeArea, String company) {
35 this.timeArea = timeArea;
36 this.company = company;
37 }
38
39 /**
40 * 显示
41 */
42 public void display() {
43 System.out.println(name + "\t" + sex + "\t" + age);
44 System.out.println("工作经历:" + timeArea + "\t" + company);
45 }
46
47 @Override
48 protected Object clone() throws CloneNotSupportedException {
49 return super.clone();
50 }
51}
客户端实现
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class Main {
6
7 public static void main(String[] args) throws CloneNotSupportedException {
8 Resume a = new Resume("程序员A");
9 a.setPersonalInfo("男","29");
10 a.setWorkExperience("1998-2001","XX 公司");
11
12 Resume b = (Resume) a.clone();
13 b.setWorkExperience("2001-2003","YY 公司");
14
15 Resume c = (Resume) a.clone();
16 c.setWorkExperience("2004-2005","ZZ 公司");
17
18 a.display();
19 b.display();
20 c.display();
21
22 }
23
24}
就这样就好了,来看下运行结果:
1程序员A 男 29
2工作经历:1998-2001 XX 公司
3程序员A 男 29
4工作经历:2001-2003 YY 公司
5程序员A 男 29
6工作经历:2004-2005 ZZ 公司
可以看到,一般在初始化的信息不发生变化的情况下,克隆是最好的方法。既隐藏了对象创建的细节,又对性能是大大的提高。而且不用重新初始化对象,而是动态地获得对象运行时的状态。
之前查的资料,clone方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。
来试试 !
Resume类中有一个设置工作经历的方法,在正式项目设计中,一般会再有一个工作经历类,当中又时间区间和公司名称等属性,Resume类直接调用,改写下程序。
创建WorkExperience类
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class WorkExperience {
6
7 private String workDate;
8 private String company;
9
10 public String getWorkDate() {
11 return workDate;
12 }
13
14 public void setWorkDate(String workDate) {
15 this.workDate = workDate;
16 }
17
18 public String getCompany() {
19 return company;
20 }
21
22 public void setCompany(String company) {
23 this.company = company;
24 }
25}
改写Resume类
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class Resume implements Cloneable{
6
7 private String name;
8 private String sex;
9 private String age;
10
11 private WorkExperience work;
12
13 public Resume(String name) {
14 this.name = name;
15 work = new WorkExperience();
16 }
17
18 /**
19 * 设置个人信息
20 *
21 * @param sex
22 * @param age
23 */
24 public void setPersonalInfo(String sex, String age) {
25 this.sex = sex;
26 this.age = age;
27 }
28
29 /**
30 * 设置工作经历
31 *
32 * @param timeArea
33 * @param company
34 */
35 public void setWorkExperience(String timeArea, String company) {
36 work.setCompany(timeArea);
37 work.setWorkDate(company);
38 }
39
40 /**
41 * 显示
42 */
43 public void display() {
44 System.out.println(name + "\t" + sex + "\t" + age);
45 System.out.println("工作经历:" + work.getCompany() + "\t" + work.getWorkDate());
46 }
47
48 @Override
49 protected Object clone() throws CloneNotSupportedException {
50 return super.clone();
51 }
52}
这是再来运行下程序,看下结果
1程序员A 男 29
2工作经历:2004-2005 ZZ 公司
3程序员A 男 29
4工作经历:2004-2005 ZZ 公司
5程序员A 男 29
6工作经历:2004-2005 ZZ 公司
这次程序没达到我们的要求,三次显示的结果都是最后一次设置的值。
为什么会发生这种情况呢?
这种情况,叫做”浅复制“,被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。但我们更需要这样的一种需求,把要复制的对象所引用的对象都复制一边,这种方式为”深复制“,深复制把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。
再来改写下程序。
让WorkEcperience也实现Cloneable接口
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class WorkExperience implements Cloneable{
6
7 private String workDate;
8 private String company;
9
10 public String getWorkDate() {
11 return workDate;
12 }
13
14 public void setWorkDate(String workDate) {
15 this.workDate = workDate;
16 }
17
18 public String getCompany() {
19 return company;
20 }
21
22 public void setCompany(String company) {
23 this.company = company;
24 }
25
26 @Override
27 protected Object clone() throws CloneNotSupportedException {
28 return super.clone();
29 }
30}
改写Resume类
1/**
2 * @author: LKP
3 * @date: 2019/3/3
4 */
5public class Resume implements Cloneable{
6
7 private String name;
8 private String sex;
9 private String age;
10
11 private WorkExperience work;
12
13 public Resume(String name) {
14 this.name = name;
15 work = new WorkExperience();
16 }
17
18 private Resume(WorkExperience work) throws CloneNotSupportedException {
19 this.work = (WorkExperience) work.clone();
20 }
21
22 /**
23 * 设置个人信息
24 *
25 * @param sex
26 * @param age
27 */
28 public void setPersonalInfo(String sex, String age) {
29 this.sex = sex;
30 this.age = age;
31 }
32
33 /**
34 * 设置工作经历
35 *
36 * @param timeArea
37 * @param company
38 */
39 public void setWorkExperience(String timeArea, String company) {
40 work.setCompany(timeArea);
41 work.setWorkDate(company);
42 }
43
44 /**
45 * 显示
46 */
47 public void display() {
48 System.out.println(name + "\t" + sex + "\t" + age);
49 System.out.println("工作经历:" + work.getCompany() + "\t" + work.getWorkDate());
50 }
51
52 @Override
53 protected Object clone() throws CloneNotSupportedException {
54 Resume obj = new Resume(this.work);
55 obj.name = this.name;
56 obj.sex = this.sex;
57 obj.age = this.age;
58 return obj;
59 }
60}
再来运行一遍
1程序员A 男 29
2工作经历:1998-2001 XX 公司
3程序员A 男 29
4工作经历:2001-2003 YY 公司
5程序员A 男 29
6工作经历:2004-2005 ZZ 公司
哈哈,结果没毛病,可以交任务了。
原型模式UML类图
总结
总结一下原型模式:
主要解决:在运行期建立和删除原型。
何时使用:
1、当一个系统应该独立于它的产品创建,构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景:
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
往期精彩回顾