简单的复制粘贴代码会对以后的程序维护造成巨大的工作量。
为了避免这种灾难的诞生,我们今天来学习原型模式,还是用代码来逐步过渡到原型模式(创建型模式)的讲解吧。
假设今天开学啦,有小明,小红,小猪入学报到!
先来一个学生档案类,有院系,入学时间,毕业时间几个属性,和属性的set/get方法
1 public class StudentFiles { 2 private String department; 3 private String admissionTime; 4 private String graduationTime; 5 6 public StudentFiles(String department, String admissionTime, String graduationTime) { 7 this.department = department; 8 this.admissionTime = admissionTime; 9 this.graduationTime = graduationTime;10 }11 12 @Override13 public String toString() {14 return "StudentFiles{" +15 "department='" + department + '\'' +16 ", admissionTime='" + admissionTime + '\'' +17 ", graduationTime='" + graduationTime + '\'' +18 '}';19 }20 }
再来一个学生类,有姓名,年龄和档案三个属性
1 public class Student { 2 private String name; 3 private int age; 4 private StudentFiles studentFiles; 5 6 public Student(String name, int age) { 7 this.name = name; 8 this.age = age; 9 }10 11 public StudentFiles getStudentFiles() {12 return studentFiles;13 }14 15 public void setStudentFiles(StudentFiles studentFiles) {16 this.studentFiles = studentFiles;17 }18 19 @Override20 public String toString() {21 return "Student{" +22 "name='" + name + '\'' +23 ", age=" + age +24 ", studentFiles=" + studentFiles +25 '}';26 }27 }28 29 30 现在开始给他们办理入学手续31 客户端代码32 public class Client {33 public static void main(String[] args) {34 StudentFiles xiaohongFiles = new StudentFiles("计算机系","2019-5-8","2023-5-8");35 Student xiaohong=new Student("小红",22);36 xiaohong.setStudentFiles(xiaohongFiles);37 38 StudentFiles xiaomingFiles = new StudentFiles("计算机系","2019-5-8","2023-5-8");39 Student xiaoming=new Student("小明",21);40 xiaoming.setStudentFiles(xiaomingFiles);41 42 StudentFiles xiaozhuFiles = new StudentFiles("计算机系","2019-5-8","2023-5-8");43 Student xiaozhu=new Student("小猪",23);44 xiaozhu.setStudentFiles(xiaozhuFiles);45 46 System.out.println(xiaohong.toString());47 System.out.println(xiaoming.toString());48 System.out.println(xiaozhu.toString());49 }50 }
结果
现在三位同学开开心心的去上学了,但是我们发现档案是个属性相同的对象。我们在创建的时候只是简单的复制粘贴过来的,复制粘贴的代码越多维护代码也就越多 。
那我们只制作一份档案试试?
1 public class Client { 2 public static void main(String[] args) { 3 StudentFiles studentFiles = new StudentFiles("计算机系","2019-5-8","2023-5-8"); 4 5 Student xiaohong=new Student("小红",22); 6 xiaohong.setStudentFiles(studentFiles); 7 8 Student xiaoming=new Student("小明",21); 9 xiaoming.setStudentFiles(studentFiles);10 11 Student xiaozhu=new Student("小猪",23);12 xiaozhu.setStudentFiles(studentFiles);13 14 System.out.println(xiaohong.toString());15 System.out.println(xiaoming.toString());16 System.out.println(xiaozhu.toString());17 }18 }
结果
看了下结果是对的,可是别开心的太早。
现在小猪同学表现一点都不好,不能再学校按时毕业了,要延期一年。1 studentFiles.setGraduationTime("2024-5-8");
结果
好了,现在小明和小红都要延期毕业了,是不是会气死其他两个同学。
分析一下原因:我们只创建了一份档案,让三个同学的档案都指向了这个档案了,三个档案是同一份档案,这当然不合乎常理了。每个人的档案都应该属于自己,而不是和别人共用。
究其发生上面情况的原因是因为我们既不想复制代码,偷懒又出现了大问题。那么存在那种我们通过代码来复制对象的可能的方法吗?
有的就是接下来出场的原型模式:
因为这个模式使用频繁,所有java已经给我们封装好了,我们只需要掌握使用即可。
首先让类实现Cloneable接口,接着重写clone方法
1 public Object clone() throws CloneNotSupportedException{2 return super.clone();3 }
此时的客户端代码
1 StudentFiles xiaohongStudentFiles= (StudentFiles) studentFiles.clone();2 StudentFiles xiaomingStudentFiles= (StudentFiles) studentFiles.clone();3 StudentFiles xiaozhuStudentFiles= (StudentFiles) studentFiles.clone();
那我们再来看看这样复制真的可以吗,假设小猪不想和小红做同学了,他要转到电信院去。
1 xiaozhuStudentFiles.setDepartment("电信院");
结果
既然我们已经做好了偷懒的准备,为什么不进行到底呢?
其实我们已经了解到来上学的同学大多都是22岁,只有极个别是其他年龄。
那我们复制学生类好了,再给每个学生都赋上他们的姓名即可。
1 public Object clone() throws CloneNotSupportedException{2 return super.clone();3 }
客户端的代码
public class Client { public static void main(String[] args) throws CloneNotSupportedException { StudentFiles studentFiles = new StudentFiles("计算机系","2019-5-8","2023-5-8"); Student student=new Student(22);//student的原型 student.setStudentFiles(studentFiles); Student xiaohong= (Student) student.clone(); xiaohong.setName("小红"); Student xiaoming=(Student) student.clone(); xiaoming.setName("小明"); xiaoming.setAge(15); Student xiaozhu=(Student) student.clone(); xiaozhu.setName("小猪"); System.out.println(xiaohong.toString()); System.out.println(xiaoming.toString()); System.out.println(xiaozhu.toString()); }}
我们发现小明原来是个神通,才15岁是同学中的特例,我们为他修改下年龄。
结果
聪明的小明提前一年修满了所有学分,他要提前毕业了。
1 StudentFiles tmp = xiaoming.getStudentFiles();2 tmp.setGraduationTime("2022-5-8");3 xiaoming.setStudentFiles(tmp);
结果
可以看到同学们都沾了小明的光提前毕业了,但是学校不允许这样的情况发生呀,我们来研究下原因吧:
我们先了解浅拷贝和深拷贝的概念
浅拷贝:只拷贝基本数据类型,对于对象属性拷贝其中的引用地址
深拷贝:复制的时候基本数据类型和对象引用都拷贝一份
很显然我们的拷贝是属于浅拷贝,我们修改年龄对其他人没有影响,但是我们修改学籍对象的时候,每个拷贝的对象都发生了修改。
那java的深拷贝是怎么实现的呢?我们修改一下Student的clon方法即可
1 public Object clone() throws CloneNotSupportedException{2 Student student=(Student)super.clone();3 student.setStudentFiles((StudentFiles)studentFiles.clone());4 return student;5 }
结果
总结:
浅拷贝:复制基本数据类型,引用类型没有进行复制步骤:
1.实现Cloneable接口
2.实现clone方法
1 public Object clone() throws CloneNotSupportedException{2 return super.clone();3 }
深拷贝:复制基本数据类型和引用类型
步骤:
1.实现Cloneable接口
2.实现clone方法
1 public Object clone() throws CloneNotSupportedException{2 Student student=(Student)super.clone();3 student.setStudentFiles((StudentFiles)studentFiles.clone());4 return student;5 }
原型模式优点:1.抽象出共同点,仅需要修改对象间的不同点2.大大减少JVM创建对象的时间
其实是有遇到过类似的情况的,只不过因为并没有学习到这里,当时使用了最笨的办法一次次的new一个对象。
比如现在有一个student的list集合创建,然后批量插入数据库。在循环处的new对象完全可以改成(Student) student.clone(),修改其中的属性即可。大大减少java徐理解创建对象的时间,同时代码也相对简洁。
到这里创建型模式(建造者模式,工厂模式,原型模式)都搞定了,还剩下单例模式还没写博客了。
单例模式十分重要,运用spring的bean的创建上,是spring IOC的重要设计模式。