摘要:缺點首先要記住原型模式的拷貝時不會執行構造函數的。源碼地址原型模式參考慕課網設計模式精講設計模式之原型模式原型模式示例六原型模式破壞單例模式
0x01.定義與類型
定義:指原型實例指定創建對象的種類,并且通過拷貝這些原型創建新的對象
特點:不需要知道任何創建的細節,不調用構造函數
類型:創建型
UML
原型模式主要用于對象的復制,它的核心是就是類圖中的原型類Prototype。Prototype類需要具備以下兩個條件:
實現Cloneable接口。在java語言有一個Cloneable接口,它的作用只有一個,就是在運行時通知虛擬機可以安全地在實現了此接口的類上使用clone方法。在java虛擬機中,只有實現了這個接口的類才可以被拷貝,否則在運行時會拋出CloneNotSupportedException異常。
重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是返回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,因此Prototype類需要將clone方法的作用域修改為public類型。
Java實現
/** * 原型模式 */ public class Prototype implements Cloneable { private String name; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; } } /** * 測試與應用類 */ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Prototype prototype = new Prototype(); prototype.setName("K.O"); Listnames = new ArrayList<>(); names.add("K.O"); prototype.setNames(names); for (int i = 0; i < 5; i ++) { Prototype p = (Prototype) prototype.clone(); p.setName("sigma"); p.getNames().add("sigma"); System.out.println(p.toString()); System.out.println(p.getName()); System.out.println(p.getNames().size()); } System.out.println(prototype.toString()); System.out.println(prototype.getName()); System.out.println(prototype.getNames().size()); } }
測試輸出結果
org.ko.prototype.basic.Prototype@1540e19d sigma 2 org.ko.prototype.basic.Prototype@677327b6 sigma 3 org.ko.prototype.basic.Prototype@14ae5a5 sigma 4 org.ko.prototype.basic.Prototype@7f31245a sigma 5 org.ko.prototype.basic.Prototype@6d6f6e28 sigma 6 org.ko.prototype.basic.Prototype@135fbaa4 K.O 6
可以看出,輸出結果中對象的地址不同(是重新創建的)
修改基本類型時,并不能影響基礎類,而引用對象只是指向的基礎類的屬性。
這里有個問題叫深拷貝,淺拷貝,后續會介紹!
原型模式的各個元素,原型模式比較簡單,元素比較少
原型接口:適用原型模式要實現原型接口,重寫里面的 clone()方法
原型類:具體產品的實現
0x02.使用場景類初始化消耗較多資源。
new產生的一個對象需要非常繁瑣的過程(數據準備、訪問權限等)。
構造函數比較復雜。
循環體中生產大量對象時。
0x03.優點原型模式性能比直接new一個對象性能高,是在內存中二進制流的拷貝,要比直接new一個對象性能好很多,特別是要在一個循環體內產生大量對象時,原型模式可能更好的體現其優點。
還可以簡化創建過程。
還有一個重要的用途就是保護性拷貝,也就是對某個對象對外可能是只讀的,為了防止外部對這個只讀對象的修改,通常可以通過返回一個對象拷貝的形式實現只讀的限制。
0x04.缺點首先要記住原型模式的拷貝時不會執行構造函數的。
clone并不一定比new一個對象快,只有當new對象比較耗時時,才考慮使用原型模式。
必須配備克隆方法。
對克隆復雜對象或對克隆出的對象進行復雜改造時,容易引入風險。
深拷貝、淺拷貝要運用得當。
要使用clone方法,類的成員變量上不要增加final關鍵字,final類型是不允許重賦值的。
0x05.樣例實現使用原型模式實現發送郵件
Java代碼實現
/** * Mail實現類 * 實現 Cloneable 接口 * 重寫 Object.clone() 方法 */ public class Mail implements Cloneable { private String name; private String emailAddress; private String content; public Mail () { System.out.println("Mail Class Constructor!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "Mail{" + "name="" + name + """ + ", emailAddress="" + emailAddress + """ + ", content="" + content + """ + "}" + super.toString(); } @Override protected Object clone() throws CloneNotSupportedException { System.out.println("clone mail object!"); return super.clone(); } } /** * Mail工具類 */ public class MailUtil { public static void sendMail (Mail mail) { String outputContent = "向{0}同學, 郵件地址:{1},郵件內容:{2}, 發送郵件成功!"; System.out.println(MessageFormat.format( outputContent, mail.getName(), mail.getEmailAddress(), mail.getContent()) ); } public static void saveOriginMailRecord (Mail mail) { System.out.println("存儲originMail記錄, originMail: " + mail.getContent()); } }
測試與應用
/** * 測試與應用 */ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Mail mail = new Mail(); mail.setContent("初始化模板"); System.out.println("初始化mail: " + mail.toString()); for (int i = 0; i < 10; i++) { Mail mailTemp = (Mail) mail.clone(); //并沒有調用Mail構造器 mailTemp.setName("K.O_" + i); mailTemp.setEmailAddress("ko.shen_" + i + "@hotmail.com"); mailTemp.setContent("恭喜您中獎了。"); MailUtil.sendMail(mailTemp); System.out.println("克隆的mailTemp: " + mailTemp.toString()); } MailUtil.saveOriginMailRecord(mail); } }
測試結果
Mail Class Constructor! 初始化mail: Mail{name="null", emailAddress="null", content="初始化模板"}org.ko.prototype.v2.Mail@1540e19d clone mail object! 向K.O_0同學, 郵件地址:ko.shen_0@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_0", emailAddress="ko.shen_0@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@677327b6 clone mail object! 向K.O_1同學, 郵件地址:ko.shen_1@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_1", emailAddress="ko.shen_1@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@14ae5a5 clone mail object! 向K.O_2同學, 郵件地址:ko.shen_2@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_2", emailAddress="ko.shen_2@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@7f31245a clone mail object! 向K.O_3同學, 郵件地址:ko.shen_3@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_3", emailAddress="ko.shen_3@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@6d6f6e28 clone mail object! 向K.O_4同學, 郵件地址:ko.shen_4@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_4", emailAddress="ko.shen_4@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@135fbaa4 clone mail object! 向K.O_5同學, 郵件地址:ko.shen_5@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_5", emailAddress="ko.shen_5@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@45ee12a7 clone mail object! 向K.O_6同學, 郵件地址:ko.shen_6@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_6", emailAddress="ko.shen_6@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@330bedb4 clone mail object! 向K.O_7同學, 郵件地址:ko.shen_7@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_7", emailAddress="ko.shen_7@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@2503dbd3 clone mail object! 向K.O_8同學, 郵件地址:ko.shen_8@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_8", emailAddress="ko.shen_8@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@4b67cf4d clone mail object! 向K.O_9同學, 郵件地址:ko.shen_9@hotmail.com,郵件內容:恭喜您中獎了。, 發送郵件成功! 克隆的mailTemp: Mail{name="K.O_9", emailAddress="ko.shen_9@hotmail.com", content="恭喜您中獎了。"}org.ko.prototype.v2.Mail@7ea987ac 存儲originMail記錄, originMail: 初始化模板
從輸出信息可以看出來,使用clone方法不需要通過構造函數創建
由于原型模式uml比較簡單,和上面基本一致,這里就不再介紹了!
0x06.擴展(深拷貝與淺拷貝) 1. 淺克隆直接實現原型模式
/** * 淺克隆 */ public class Pig1 implements Cloneable { private String name; private Date birthday; public Pig1(String name, Date birthday) { this.name = name; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Pig1{" + "name="" + name + """ + ", birthday=" + birthday + "}" + super.toString(); } }
測試類
public class Test1 { public static void main(String[] args) throws CloneNotSupportedException { //淺克隆, 沒辦法克隆引用對象 Date birthday = new Date(0l); Pig1 pig1 = new Pig1("佩奇", birthday); Pig1 pig2 = (Pig1) pig1.clone(); System.out.println(pig1); System.out.println(pig2); System.out.println("-------"); pig1.getBirthday().setTime(666666666666L); System.out.println(pig1); System.out.println(pig2); } }
輸出日志
Pig1{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig1@6d6f6e28 Pig1{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig1@135fbaa4 ------- Pig1{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig1@6d6f6e28 Pig1{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig1@135fbaa4
如上所示,修改pig1中的birthday,pig2中的也響應了變化,所以直接使用clone方法返回的對象中的引用變量并沒有重新創建而是直接復用的原有對象中的變量。
由此得出結論:clone方法默認使用的是淺拷貝。
如果想要引用變量也全部復制?
2.深拷貝其實深克隆只是自己實現了引用變量的創建,請看實現:
/** * 深克隆 */ public class Pig2 implements Cloneable { private String name; private Date birthday; public Pig2(String name, Date birthday) { this.name = name; this.birthday = birthday; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override protected Object clone() throws CloneNotSupportedException { Pig2 pig2 = (Pig2) super.clone(); //深克隆, 要自己去做對象引用的克隆 pig2.birthday = (Date) pig2.birthday.clone(); return pig2; } @Override public String toString() { return "Pig1{" + "name="" + name + """ + ", birthday=" + birthday + "}" + super.toString(); } }
測試類
public class Test2 { public static void main(String[] args) throws CloneNotSupportedException { //深克隆 Date birthday = new Date(0l); Pig2 p1 = new Pig2("佩奇", birthday); Pig2 p2 = (Pig2) p1.clone(); System.out.println(p1); System.out.println(p2); System.out.println("-------"); p1.getBirthday().setTime(666666666666L); System.out.println(p1); System.out.println(p2); } }
輸出日志
Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@6d6f6e28 Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@135fbaa4 ------- Pig2{name="佩奇", birthday=Sat Feb 16 09:11:06 CST 1991}org.ko.prototype.clone.Pig2@6d6f6e28 Pig2{name="佩奇", birthday=Thu Jan 01 08:00:00 CST 1970}org.ko.prototype.clone.Pig2@135fbaa4
可以看出,修改了pig1的時間,pig2并沒有跟著響應。所以深拷貝完成。
0x07.原型模式對單例模式的破壞當單例對象實現了clone方法時,會返回多個實例,請看實現:
/** * 簡單的餓漢式單例 */ public class StaticInnerClassSingleton implements Cloneable { /** * 看靜態類的初始化鎖那個線程可以拿到 */ private static class InnerClass { private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance() { return InnerClass.staticInnerClassSingleton; } private StaticInnerClassSingleton() { if (InnerClass.staticInnerClassSingleton != null) { throw new RuntimeException("單例對象禁止反射調用"); } } /** * 直接重寫clone方法 * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { //獲取單例對象 StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance(); System.out.println(singleton.toString()); //clone獲取克隆對象 StaticInnerClassSingleton singleton1 = (StaticInnerClassSingleton) singleton.clone(); System.out.println(singleton1.toString()); } }
輸出日志
org.ko.prototype.singleton.StaticInnerClassSingleton@1540e19d org.ko.prototype.singleton.StaticInnerClassSingleton@677327b6
根據日志可以看出,單例模式被破壞掉。
重寫clone()方法,直接返回INSTANCE對象解決原型模式對單例模式的破壞
/** * 簡單的餓漢式單例 */ public class StaticInnerClassSingleton1 implements Cloneable { /** * 看靜態類的初始化鎖那個線程可以拿到 */ private static class InnerClass { private static StaticInnerClassSingleton1 staticInnerClassSingleton = new StaticInnerClassSingleton1(); } public static StaticInnerClassSingleton1 getInstance() { return InnerClass.staticInnerClassSingleton; } private StaticInnerClassSingleton1() { if (InnerClass.staticInnerClassSingleton != null) { throw new RuntimeException("單例對象禁止反射調用"); } } /** * 修改克隆方法,返回單例對象 * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { return InnerClass.staticInnerClassSingleton; } } /** * 修改后的測試類 */ public class Test1 { public static void main(String[] args) throws CloneNotSupportedException { //獲取單例對象 StaticInnerClassSingleton1 singleton1 = StaticInnerClassSingleton1.getInstance(); System.out.println(singleton1.toString()); //獲取clone對象 StaticInnerClassSingleton1 singleton2 = (StaticInnerClassSingleton1) singleton1.clone(); System.out.println(singleton2.toString()); } }
輸出日志
org.ko.prototype.singleton.StaticInnerClassSingleton1@1540e19d org.ko.prototype.singleton.StaticInnerClassSingleton1@1540e19d
可以看出,返回的對象地址時一致的。這樣就解決了原型對單例模式的破壞。
0x08.源碼地址原型模式: https://github.com/sigmako/design-pattern/tree/master/prototype
0x09.參考慕課網設計模式精講: https://coding.imooc.com/class/270.html
設計模式之原型模式: https://blog.csdn.net/chenliguan/article/details/69855738
C06 原型模式 示例(六) 原型模式破壞單例模式: https://blog.csdn.net/weixin_33669968/article/details/88889565
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77862.html
摘要:如果一個對象的初始化需要很多其他對象的數據準備或其他資源的繁瑣計算,那么可以使用原型模式。當需要一個對象的大量公共信息,少量字段進行個性化設置的時候,也可以使用原型模式拷貝出現有對象的副本進行加工處理。 1、什么是原型模式Specify the kinds of objects to create using a prot...
摘要:三種使用構造函數創建對象的方法和的作用都是在某個特殊對象的作用域中調用函數。這種方式還支持向構造函數傳遞參數。叫法上把函數叫做構造函數,其他無區別適用情境可以在特殊的情況下用來為對象創建構造函數。 一、工廠模式 工廠模式:使用字面量和object構造函數會有很多重復代碼,在此基礎上改進showImg(https://segmentfault.com/img/bVbmKxb?w=456&...
摘要:繼續分享設計模式的公開課,這是第四篇創建型模式之原型模式設計模式的一般介紹在第一篇文章講了,不了解的可以先看看。設計模式的第一部分,創建型模式就總結完了。下面還有兩部分結構型設計模式和行為型設計模式稍后繼續。 繼續分享設計模式的公開課,這是第四篇創建型模式之原型模式 設計模式的一般介紹在第一篇文章講了,不了解的可以先看看。 原型模式: 用原型實例指定創建對象的種類,并且通過拷貝這個...
摘要:可以用刪除實例對象中自己添加的屬性可以確定屬性是原型中還是實例對象中,當時實例對象中時,返回的是操作符,有兩種使用方式,單獨使用和循環中。單獨使用,通過對象能夠訪問屬性時返回,無論時在原型中還是實例對象中。 原型模式,每個創建的對象都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。 ------------...
摘要:深入系列第十四篇,講解創建對象的各種方式,以及優缺點。也就是說打著構造函數的幌子掛羊頭賣狗肉,你看創建的實例使用都無法指向構造函數這樣方法可以在特殊情況下使用。 JavaScript深入系列第十四篇,講解創建對象的各種方式,以及優缺點。 寫在前面 這篇文章講解創建對象的各種方式,以及優缺點。 但是注意: 這篇文章更像是筆記,因為《JavaScript高級程序設計》寫得真是太好了! 1....
閱讀 1370·2021-11-22 09:34
閱讀 2580·2021-11-12 10:36
閱讀 1111·2021-11-11 16:55
閱讀 2324·2020-06-22 14:43
閱讀 1457·2019-08-30 15:55
閱讀 1974·2019-08-30 15:53
閱讀 1764·2019-08-30 10:50
閱讀 1217·2019-08-29 12:15