摘要:定義給出的原型模式定義如下使用原型實(shí)例指定將要?jiǎng)?chuàng)建的對(duì)象類型,通過(guò)復(fù)制這個(gè)實(shí)例創(chuàng)建新的對(duì)象。具體原型類角色負(fù)責(zé)實(shí)現(xiàn)復(fù)制現(xiàn)有實(shí)例并生成新實(shí)例的方法。
Java面試通關(guān)手冊(cè)(Java學(xué)習(xí)指南,歡迎Star,會(huì)一直完善下去,歡迎建議和指導(dǎo)):https://github.com/Snailclimb/Java_Guide
系列文章回顧:設(shè)計(jì)模式專欄
深入理解單例模式
深入理解工廠模式
深入理解建造者模式 ——組裝復(fù)雜的實(shí)例
歷史文章推薦:一只準(zhǔn)程序猿的嘮叨
可能是最漂亮的Spring事務(wù)管理詳解
Java多線程學(xué)習(xí)(八)線程池與Executor 框架
面試中關(guān)于Redis的問題看這篇就夠了
目錄:[TOC]
一 原型模式介紹在面向?qū)ο笙到y(tǒng)中,使用原型模式來(lái)復(fù)制一個(gè)對(duì)象自身,從而克隆出多個(gè)與原型對(duì)象一模一樣的對(duì)象。
另外在軟件系統(tǒng)中,有些對(duì)象的創(chuàng)建過(guò)程較為復(fù)雜,而且有時(shí)候需要頻繁創(chuàng)建,原型模式通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所要?jiǎng)?chuàng)建的對(duì)象的類型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建出更多同類型的對(duì)象,這就是原型模式的意圖所在。
1.1 定義GOF給出的原型模式定義如下:
Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype. (使用原型實(shí)例指定將要?jiǎng)?chuàng)建的對(duì)象類型,通過(guò)復(fù)制這個(gè)實(shí)例創(chuàng)建新的對(duì)象。)1.2 原型模式適用場(chǎng)景
我們現(xiàn)在一般會(huì)使用new關(guān)鍵字指定類名生成類的實(shí)例(PS:我們以前使用java.lang.Cloneable的一個(gè)很大原因是使用new創(chuàng)建對(duì)象的速度相對(duì)來(lái)說(shuō)會(huì)慢一些,隨著JVM性能的提升,new的速度和Object的clone()方法的速度差不多了。)。
使用new關(guān)鍵字創(chuàng)建類的時(shí)候必須指定類名,但是在開發(fā)過(guò)程中也會(huì)有“在不指定類名的前提下生成實(shí)例”的需求。例如,在下面這些情況下,就需要根據(jù)現(xiàn)有的實(shí)例來(lái)生成新的實(shí)例。
1) 對(duì)象種類繁多,無(wú)法將他們整合到一個(gè)類的時(shí)候;
2) 難以根據(jù)類生成實(shí)例時(shí);
3) 想解耦框架與生成的實(shí)例時(shí)。
如果想要讓生成實(shí)例的框架不再依賴于具體的類,這時(shí),不能指定類名來(lái)生成實(shí)例,而要事先“注冊(cè)”一個(gè)“原型”實(shí)例,然后通過(guò)復(fù)制該實(shí)例來(lái)生成新的實(shí)例。
1.3 模式分析在原型模式結(jié)構(gòu)中定義了一個(gè)抽象原型類,所有的Java類都繼承自 java.lang.Object,而Object類提供一個(gè)clone()方法,可以將一個(gè)Java對(duì)象復(fù)制一份。因此在Java中可以直接使用Object提供的clone()方法來(lái)實(shí)現(xiàn)對(duì)象的克隆,Java語(yǔ)言中的原型模式實(shí)現(xiàn)很簡(jiǎn)單。
能夠?qū)崿F(xiàn)克隆的Java類必須實(shí)現(xiàn)一個(gè)標(biāo)識(shí)接口Cloneable,表示這個(gè)Java類支持復(fù)制。如果一個(gè)類沒有實(shí)現(xiàn)這個(gè)接口但是調(diào)用了clone()方法,Java編譯器將拋出一個(gè)CloneNotSupportedException異常。
注意: `java.lang.Cloneable 只是起到告訴程序可以調(diào)用clone方法的作用,它本身并沒有定義任何方法。
在使用原型模式克隆對(duì)象時(shí),根據(jù)其成員對(duì)象是否也克隆,原型模式可以分為兩種形式:深克隆 和 淺克隆 。
關(guān)于深克隆 和 淺克隆 的詳細(xì)內(nèi)容可以參考:詳解Java中的clone方法
1.4 模式優(yōu)缺點(diǎn)分析原型模式的優(yōu)點(diǎn):
當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí),使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過(guò)程,通過(guò)一個(gè)已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
可以動(dòng)態(tài)增加或減少產(chǎn)品類。
原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu)。
可以使用深克隆的方式保存對(duì)象的狀態(tài)。
原型模式的缺點(diǎn):
需要為每一個(gè)類配備一個(gè)克隆方法,而且這個(gè)克隆方法需要對(duì)類的功能進(jìn)行通盤考慮,這對(duì)全新的類來(lái)說(shuō)不是很難,但對(duì)已有的類進(jìn)行改造時(shí),不一定是件容易的事,必須修改其源代碼,違背了“開閉原則”。
在實(shí)現(xiàn)深克隆時(shí)需要編寫較為復(fù)雜的代碼。
二 示例程序下面示例程序的作用是將字符串放入方框中顯示出來(lái)或者是加了下劃線顯示出來(lái)。
類和接口一覽表:
示例程序類圖:
Product接口是復(fù)制功能接口,該接口繼承了java.lang.Cloneable(只有實(shí)現(xiàn)了該接口的類的實(shí)例才可以調(diào)用clone()方法復(fù)制實(shí)例,否則會(huì)拋出異常).
另外需要注意:`java.lang.Cloneable 只是起到告訴程序可以調(diào)用clone方法的作用,它本身并沒有定義任何方法。
package prototype_pattern; public interface Product extends Cloneable{ //use方法是用于“使用”的方法,具體怎么“使用”,則被交給子類去實(shí)現(xiàn)。 public abstract void use(String s); //creatClone方法是用于復(fù)制實(shí)例的方法 public abstract Product creatClone(); }2.2 Manager類(Client)
Manager類使用Product接口來(lái)復(fù)制實(shí)例。
Product接口以及Manager類的代碼完全沒有出現(xiàn)在MessageBox類和UnderlinePen類的名字,因此這意味著我們可以獨(dú)立地修改Product接口以及Manager類,不受MessageBox類和UnderlinePen類的影響。這是非常重要的,因?yàn)?一旦在類中使用到了別的類名,就意味著該類與其他類緊密的地耦合在了一起 。在Manager類中,并沒有寫明具體的類名, 僅僅使用了Product這個(gè)接口名。也就是說(shuō),Product接口成為了連接Manager類與其他具體類之間的橋梁。
package prototype_pattern; import java.util.HashMap; public class Manager { //保存實(shí)例的“名字”和“實(shí)例”之間的對(duì)應(yīng)關(guān)系 private HashMap2.3 MessageBox類(ConcreteProtorype)showcase=new HashMap (); //register方法將接收到的一組“名字”和“Product接口”注冊(cè)到showcase中。這里Product是實(shí)現(xiàn)Product接口的實(shí)例,具體還未確定 public void register(String name ,Product product){ showcase.put(name, product); } public Product create(String productname){ Product p=showcase.get(productname); return p.creatClone(); } }
裝飾方框樣式的具體原型,實(shí)現(xiàn)了 Product接口,實(shí)現(xiàn)復(fù)制現(xiàn)有實(shí)例并生成新實(shí)例的方法。
package prototype_pattern; public class MessageBox implements Product { //保存的是裝飾方框使用的字符樣式 private char decochar; public MessageBox(char decochar) { this.decochar = decochar; } @Override public void use(String s) { int length=s.getBytes().length; for (int i = 0; i < length+4; i++) { System.out.print(decochar); } System.out.println(""); System.out.println(decochar+" "+s+" "+decochar); for (int i = 0; i < length+4; i++) { System.out.print(decochar); } System.out.println(""); } //該方法用于復(fù)制自己 @Override public Product creatClone() { Product p=null; try { p=(Product) clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } }
只有類自己(或是它的子類)能夠調(diào)用Java語(yǔ)言中定義的clone方法。當(dāng)其他類要求復(fù)制實(shí)例時(shí),必須先調(diào)用createClone這樣的方法,然后在該方法內(nèi)部在調(diào)用clone方法。
2.4 UnderlinePen類(ConcreteProtorype)下劃線樣式的具體原型,實(shí)現(xiàn)了Product接口,用于實(shí)現(xiàn)復(fù)制現(xiàn)有實(shí)例并生成新實(shí)例的方法。UnderlinePen類的實(shí)現(xiàn)幾乎和 MessageBox類一樣,不同的可能只是use方法的實(shí)現(xiàn)。
package prototype_pattern; public class UnderlinePen implements Product { private char ulchar; public UnderlinePen(char ulchar) { this.ulchar = ulchar; } @Override public void use(String s) { int length = s.getBytes().length; System.out.println("""+s+"""); for (int i = 0; i2.5 Main類 Main類首先生成Manager實(shí)例。接著,在Manager實(shí)例中通過(guò)`register方法注冊(cè)了UnderlinePen類的實(shí)例(帶名字)和MessageBox類的實(shí)例(帶名字)。
package prototype_pattern; public class Main { public static void main(String[] args) { Manager manager = new Manager(); UnderlinePen underlinePen=new UnderlinePen("~"); MessageBox mbox=new MessageBox("*"); MessageBox sbox=new MessageBox("/"); manager.register("Strong message", underlinePen); manager.register("Waring Box", mbox); manager.register("Slash Box", sbox); Product p1=manager.create("Strong message"); p1.use("hello world"); Product p2=manager.create("Waring Box"); p2.use("hello world"); Product p3=manager.create("Slash Box"); p3.use("hello world"); } }運(yùn)行結(jié)果:
三 原型模式的角色分析通過(guò)上面的例子,相信大家對(duì)于原型模式有了更進(jìn)一步的認(rèn)識(shí),下面我們看看原型模式的幾個(gè)登場(chǎng)角色。
3.1 Prototype(抽象原型類)Product角色負(fù)責(zé)定義用于復(fù)制現(xiàn)有實(shí)例來(lái)生成新實(shí)例的方法。在示例程序中的Product接口就是該角色。
3.2 ConcretePrototype(具體原型類)ConcretePrototype角色負(fù)責(zé)實(shí)現(xiàn)復(fù)制現(xiàn)有實(shí)例并生成新實(shí)例的方法。在示例程序中,MessageBox和UnderlinePen都是該角色。
3.3 Client(客戶類/使用者)Client角色負(fù)責(zé)使用復(fù)制實(shí)例的方法生成新的實(shí)例。在示例程序中,Manager類扮演的就是該角色。
Prototype模式的類圖:
四 原型模式的實(shí)際應(yīng)用案例(1) 原型模式應(yīng)用于很多軟件中,如果每次創(chuàng)建一個(gè)對(duì)象要花大量時(shí)間,原型模式是最好的解決方案。很多軟件提供的復(fù)制(Ctrl + C)和粘貼(Ctrl + V)操作就是原型模式的應(yīng)用,復(fù)制得到的對(duì)象與原型對(duì)象是兩個(gè)類型相同但內(nèi)存地址不同的對(duì)象,通過(guò)原型模式可以大大提高對(duì)象的創(chuàng)建效率。
(2) 在Struts2中為了保證線程的安全性,Action對(duì)象的創(chuàng)建使用了原型模式,訪問一個(gè)已經(jīng)存在的`Action對(duì)象時(shí)將通過(guò)克隆的方式創(chuàng)建出一個(gè)新的對(duì)象,從而保證其中定義的變量無(wú)須進(jìn)行加鎖實(shí)現(xiàn)同步,每一個(gè)Action中都有自己的成員變量,避免Struts1因使用單例模式而導(dǎo)致的并發(fā)和同步問題。
(3) 在Spring中,用戶也可以采用原型模式來(lái)創(chuàng)建新的bean實(shí)例,從而實(shí)現(xiàn)每次獲取的是通過(guò)克隆生成的新實(shí)例,對(duì)其進(jìn)行修改時(shí)對(duì)原有實(shí)例對(duì)象不造成任何影響。
五 總結(jié)本文主要介紹了:什么是原型模式、原型模式的優(yōu)缺點(diǎn)以及使用場(chǎng)景。另外,簡(jiǎn)單介紹了深拷貝和淺拷貝以及原型模式的實(shí)際應(yīng)用案例。
參考:
《圖解設(shè)計(jì)模式》
歡迎關(guān)注我的微信公眾號(hào):"Java面試通關(guān)手冊(cè)"(一個(gè)有溫度的微信公眾號(hào),無(wú)廣告,單純技術(shù)分享,期待與你共同進(jìn)步~~~堅(jiān)持原創(chuàng),分享美文,分享各種Java學(xué)習(xí)資源。)最后,就是使用阿里云服務(wù)器一段時(shí)間后,感覺阿里云真的很不錯(cuò),就申請(qǐng)做了阿里云大使,然后這是我的優(yōu)惠券地址.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71210.html
摘要:首先,需要來(lái)理清一些基礎(chǔ)的計(jì)算機(jī)編程概念編程哲學(xué)與設(shè)計(jì)模式計(jì)算機(jī)編程理念源自于對(duì)現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過(guò)程式和函數(shù)式編程。 JavaScript 中的原型機(jī)制一直以來(lái)都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因?yàn)榻^大多數(shù)人沒有想要深刻理解這個(gè)機(jī)制的內(nèi)涵,以及越來(lái)越多的開發(fā)者缺乏計(jì)算機(jī)編程相關(guān)的基礎(chǔ)知識(shí)。對(duì)于這樣的開發(fā)者來(lái)說(shuō) J...
摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。 開篇: 在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來(lái)相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_始B...
摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。 開篇: 在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來(lái)相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_始B...
摘要:上圖中的在原型繼承稱作構(gòu)造器。構(gòu)造器就是一個(gè)普通的函數(shù),但是將操作符用到構(gòu)造器上時(shí),它會(huì)執(zhí)行一個(gè)叫的過(guò)程。從第條可以看到,構(gòu)造器生成的對(duì)象的屬性會(huì)指向構(gòu)造器的值,這就是我們構(gòu)造原型鏈的關(guān)鍵。 基于類的繼承是大多數(shù)人所熟悉的,也是比較容易理解的。當(dāng)我們形成類型繼承的思維定勢(shì)后,再次接觸原型繼承可能會(huì)覺得有些奇怪并難以理解。你更可能會(huì)吐槽,原型繼承根本就不能叫做繼承,一點(diǎn)都不面向?qū)ο蟆1救?..
摘要:上圖中的在原型繼承稱作構(gòu)造器。構(gòu)造器就是一個(gè)普通的函數(shù),但是將操作符用到構(gòu)造器上時(shí),它會(huì)執(zhí)行一個(gè)叫的過(guò)程。從第條可以看到,構(gòu)造器生成的對(duì)象的屬性會(huì)指向構(gòu)造器的值,這就是我們構(gòu)造原型鏈的關(guān)鍵。 基于類的繼承是大多數(shù)人所熟悉的,也是比較容易理解的。當(dāng)我們形成類型繼承的思維定勢(shì)后,再次接觸原型繼承可能會(huì)覺得有些奇怪并難以理解。你更可能會(huì)吐槽,原型繼承根本就不能叫做繼承,一點(diǎn)都不面向?qū)ο?。本?..
閱讀 3050·2021-11-24 10:21
閱讀 1622·2021-10-11 10:57
閱讀 2838·2021-09-22 15:24
閱讀 2723·2021-09-22 14:58
閱讀 2355·2019-08-30 13:16
閱讀 3522·2019-08-29 13:05
閱讀 3442·2019-08-29 12:14
閱讀 3482·2019-08-27 10:55