简单的复制粘贴代码会对以后的程序维护造成巨大的工作量。

为了避免这种灾难的诞生,我们今天来学习原型模式,还是用代码来逐步过渡到原型模式(创建型模式)的讲解吧。

 

假设今天开学啦,有小明,小红,小猪入学报到!

先来一个学生档案类,有院系,入学时间,毕业时间几个属性,和属性的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     @Override
13     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     @Override
20     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的重要设计模式。