簡單的復制粘貼代碼會對以后的程序維護造成巨大的工作量。
為了避免這種災難的誕生,我們今天來學習原型模式,還是用代碼來逐步過渡到原型模式(創建型模式)的講解吧。
假設今天開學啦,有小明,小紅,小豬入學報到!
先來一個學生檔案類,有院系,入學時間,畢業時間幾個屬性,和屬性的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的重要設計模式。