【设计模式】单例模式-生成器模式-原型模式
前面的幾種工廠模式,主要用于選擇實(shí)現(xiàn),這里的三種模式:單例模式、生成器模式、原型模式,主要用于生成對(duì)象,在GoF的劃分中,這是創(chuàng)建型的五種模式(不包括簡(jiǎn)單工廠,前面提到過(guò),這不是一個(gè)標(biāo)準(zhǔn)意義上的設(shè)計(jì)模式)。
1.單例模式
2.生成器模式
3.原型模式
?
單例模式(Singleton)
1. 應(yīng)用場(chǎng)景及問(wèn)題描述
在大型項(xiàng)目開(kāi)發(fā)中,許多數(shù)據(jù)要保存在屬性文件或數(shù)據(jù)庫(kù)里,其中有那么一類(lèi)數(shù)據(jù),我們會(huì)頻繁地訪問(wèn),但是這些數(shù)據(jù)挺少而且不經(jīng)常變化,那么在不同的方法、不同的時(shí)間,每次需要訪問(wèn)的時(shí)候都去創(chuàng)建一個(gè)數(shù)據(jù)連接對(duì)象,重新去數(shù)據(jù)源讀取數(shù)據(jù),開(kāi)銷(xiāo)很大而且不值得,就有這么一種解決方案,把這些數(shù)據(jù)提前取得存到某個(gè)類(lèi)的變量里,讓整個(gè)應(yīng)用程序中只有一個(gè)該類(lèi)的實(shí)例,我需要的時(shí)候就去獲取這個(gè)實(shí)例。
現(xiàn)在來(lái)描述一個(gè)場(chǎng)景,一個(gè)網(wǎng)站的用戶(hù)有多種權(quán)限,比如普通用戶(hù)、管理員、VIP1、VIP2,我在應(yīng)用程序中經(jīng)常需要根據(jù)權(quán)限的ID去得到對(duì)應(yīng)身份的文字描述,也經(jīng)常需要根據(jù)這個(gè)描述去獲取這種權(quán)限的ID,訪問(wèn)非常頻繁。
2. 單例模式
單例模式定義:一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)獲取它的全局訪問(wèn)點(diǎn)。
首先,只要一個(gè)類(lèi)有公有的構(gòu)造函數(shù),就不能阻擋這個(gè)類(lèi)對(duì)象的多次創(chuàng)建,所以要將構(gòu)造函數(shù)私有化,在內(nèi)部創(chuàng)建一個(gè)本類(lèi)的靜態(tài)實(shí)例,并通過(guò)一個(gè)靜態(tài)的方法來(lái)從外部獲得這個(gè)已經(jīng)創(chuàng)建好的實(shí)例。
單例模式分為懶漢式和餓漢式兩種實(shí)現(xiàn),懶漢式是一種懶的實(shí)現(xiàn)方式,這個(gè)靜態(tài)實(shí)例不在剛解析這個(gè)類(lèi)的時(shí)候就創(chuàng)建,而是等真正去要這個(gè)實(shí)例的時(shí)候才創(chuàng)建,實(shí)現(xiàn)權(quán)限ID與名稱(chēng)雙向訪問(wèn)的類(lèi)的完整定義如下(注:這里用到了一個(gè)雙向的Map類(lèi)DualHashbidiMap,在commons-collections-3.2.1.jar包里):
public class IDAssisstant {private IRoleDAO roleDao; //通過(guò)依賴(lài)注入獲得實(shí)例,訪問(wèn)數(shù)據(jù)庫(kù)權(quán)限表private static DualHashBidiMap roleMap = new DualHashBidiMap();private static IDAssisstant instance;public static synchronized IDAssisstant getInstance(){if(instance == null)instance = new IDAssisstant();return instance;}private IDAssisstant() {List<Role> rlist = roleDao.findAll();for (Role r : rlist) {roleMap.put(r.getId(), r.getName());}}public void reInitRoleMap() throws SeeWorldException {List<Role> rlist = roleDao.findAll("",false);roleMap.clear();for (Role r : rlist) {roleMap.put(r.getId(), r.getName());}}public void setRoleDao(IRoleDAO roleDao) {this.roleDao = roleDao;}public String getRoleName(String roleId) {return (String) roleMap.get(roleId);}public String getRoleID(String roleName) {return (String) roleMap.getKey(roleName);} }解釋一下,這個(gè)類(lèi)中的roleMap變量把數(shù)據(jù)庫(kù)用戶(hù)權(quán)限表(role)中的數(shù)據(jù)一次全部讀取出來(lái),每次需要根據(jù)ID來(lái)獲取權(quán)限名或者根據(jù)權(quán)限名來(lái)獲取ID的時(shí)候直接調(diào)用getRoleName個(gè)getRoleID這樣的方法來(lái)獲取,roleMap是一個(gè)雙向Map,可以方便地根據(jù)鍵查找值或者根據(jù)值查找鍵。仔細(xì)看一下這兩行:
private static IDAssisstant instance; public static synchronized IDAssisstant getInstance(){if(instance == null)instance = new IDAssisstant();return instance; }一開(kāi)始并沒(méi)有創(chuàng)建實(shí)例,第一次真正去調(diào)用IDAssistance.getInstance()來(lái)獲取實(shí)例的時(shí)候才去創(chuàng)建,當(dāng)然這會(huì)涉及一個(gè)并發(fā)訪問(wèn)的問(wèn)題,并發(fā)訪問(wèn)時(shí)可能會(huì)多次調(diào)用IDAssistant的構(gòu)造函數(shù),所以加關(guān)鍵詞synchronized來(lái)進(jìn)行修飾。
而餓漢式是一上來(lái)就創(chuàng)建,省掉了每次申請(qǐng)實(shí)例的時(shí)候的那個(gè)if判斷,缺點(diǎn)是不管用不用得到這個(gè)instance實(shí)例,都會(huì)在一開(kāi)始解析這個(gè)類(lèi)的時(shí)候就創(chuàng)建它,修改一下上面這幾行就得到餓漢式的實(shí)現(xiàn):
private static IDAssisstant instance = new IDAssisstant(); public static synchronized IDAssisstant getInstance(){return instance; }餓漢式的實(shí)現(xiàn)是線程安全的,沒(méi)有并發(fā)的問(wèn)題。
單例模式中變量和方法的命名,一般都是用instance和getInstance()。
3. 延遲加載和緩存思想
懶漢模式體現(xiàn)了一種延遲加載的思想,所謂延遲加載,顧名思義,就是一開(kāi)始不加載,真正需要的時(shí)候才去加載。
同時(shí)它也可以體現(xiàn)一種緩存的思想,這是種空間換時(shí)間的思想,在上面的實(shí)現(xiàn)中,instance對(duì)象其實(shí)就可以看做是緩存實(shí)例。單例模式并不一定說(shuō)必須只創(chuàng)建一個(gè)實(shí)例,比如這個(gè)實(shí)例的訪問(wèn)過(guò)于頻繁,一個(gè)實(shí)例忙不過(guò)來(lái),那就可能要用兩個(gè)、三個(gè)或者更多,在程序里進(jìn)行調(diào)度,決定把哪個(gè)實(shí)例返回給客戶(hù),簡(jiǎn)單實(shí)現(xiàn)如下:
public class Singleton {private final static String DEFAULT_PREFIX = "Cache";//Map容易用來(lái)緩存實(shí)例private static Map<String, Singleton> map = new HashMap<String, Singleton>();//用來(lái)記錄當(dāng)前正在使用的實(shí)例標(biāo)號(hào)private static int current = 0;//定義實(shí)例的最大數(shù)目private final static int MAX_NUM_OF_INSTANCE = 5;private Singleton(){}public static Singleton getInstance(){String key = DEFAULT_PREFIX + current;Singleton s = map.get(key);if(s == null){s = new Singleton();map.put(key, s);}current++;if(current >= MAX_NUM_OF_INSTANCE)current = 0;return s;} }注意:這里只是提供一種思路,顯而易見(jiàn),這個(gè)方法是線程不安全的。
4. Java中更好地單例實(shí)現(xiàn)方式
餓漢式浪費(fèi)內(nèi)存,如果程序運(yùn)行很久都沒(méi)有訪問(wèn)這個(gè)單例,但這個(gè)實(shí)例早已創(chuàng)建,內(nèi)存就會(huì)一直被占用著,而懶漢式存在線程安全問(wèn)題,用synchronized關(guān)鍵詞解決后鎖住了整個(gè)方法,效率會(huì)下降很多,在Java中有這么兩種實(shí)現(xiàn)方式,同樣可以實(shí)現(xiàn)單例,但是卻擁有更好的效率,先來(lái)看第一種,是借助類(lèi)級(jí)內(nèi)部類(lèi)來(lái)實(shí)現(xiàn)。
首先看一下類(lèi)級(jí)內(nèi)部類(lèi)的概念:類(lèi)級(jí)內(nèi)部類(lèi)就是有static關(guān)鍵詞修飾的內(nèi)部類(lèi),沒(méi)有static修飾的叫做對(duì)象級(jí)內(nèi)部類(lèi),類(lèi)級(jí)內(nèi)部類(lèi)是外部類(lèi)的靜態(tài)成員,與外部類(lèi)對(duì)象無(wú)依賴(lài)關(guān)系,可以定義靜態(tài)方法來(lái)引用外部類(lèi)中靜態(tài)成員(也只能引用外部類(lèi)的靜態(tài)成員,不能引用非靜態(tài)成員),最重要的,類(lèi)級(jí)內(nèi)部類(lèi)相當(dāng)于外部類(lèi)的成員,只有在第一次被使用時(shí)才被裝載。
再來(lái)看看同步,JVM在在靜態(tài)字段或靜態(tài)代碼塊中初始化數(shù)據(jù)時(shí)隱式地為我們處理了并發(fā)控制的問(wèn)題。
現(xiàn)在就要利用這兩個(gè)概念,我們可以采用類(lèi)級(jí)內(nèi)部類(lèi),只要不使用這個(gè)類(lèi)級(jí)內(nèi)部類(lèi),就不會(huì)創(chuàng)建對(duì)象實(shí)例,而且采用靜態(tài)初始化器的方式可以由JVM來(lái)保證并發(fā)問(wèn)題,從而實(shí)現(xiàn)延遲加載和線程安全。先來(lái)看代碼:
public class Singleton {//類(lèi)級(jí)內(nèi)部類(lèi),與外部類(lèi)實(shí)例無(wú)關(guān),被調(diào)用時(shí)才會(huì)裝載,從而實(shí)現(xiàn)延遲加載private static class SingletonHolder{//靜態(tài)初始化器,JVM保證線程安全private static Singleton instance = new Singleton();}private Singleton(){}public static Singleton getInstance(){return SingletonHolder.instance;} }當(dāng)getInstance方法第一次被調(diào)用的時(shí)候,第一次讀取SingletonHolder.instance,這時(shí)候SingletonHolder類(lèi)才初始化,此時(shí)初始化靜態(tài)域創(chuàng)建了Singleton的實(shí)例,因?yàn)槭且粋€(gè)靜態(tài)域,只會(huì)在虛擬機(jī)裝載類(lèi)的時(shí)候初始化一次,由虛擬機(jī)保證線程安全性。
第二種單例的實(shí)現(xiàn)方式是使用枚舉類(lèi)型,這是一種更為巧妙的方式,要知道,Java的枚舉類(lèi)型實(shí)質(zhì)上是功能齊全的類(lèi),通過(guò)共有的靜態(tài)final域?yàn)槊總€(gè)枚舉常量導(dǎo)出實(shí)例的類(lèi),可以看做是單例的泛型化。
用枚舉類(lèi)進(jìn)行實(shí)現(xiàn),只需要編寫(xiě)一個(gè)包含單個(gè)元素的枚舉類(lèi)型即可,用這種方法控制簡(jiǎn)潔,無(wú)償提供序列化機(jī)制,由JVM從根本上提供保障,絕對(duì)防止多次實(shí)例化,是更簡(jiǎn)潔、高效、安全的實(shí)現(xiàn)方式:
public enum Singleton {//定義一個(gè)元素的枚舉,代表一個(gè)Singleton的實(shí)例 uniqueInstance;//定義單例自己的操作public void someOperation(){//操作 } }5. 模式說(shuō)明
單例模式的實(shí)質(zhì)就是控制實(shí)例的數(shù)目,抽象工廠中的具體工廠類(lèi)就可以實(shí)現(xiàn)為單例,當(dāng)需要控制一個(gè)類(lèi)的實(shí)例數(shù)目,而且客戶(hù)只能從一個(gè)全局訪問(wèn)點(diǎn)訪問(wèn)它時(shí)選用單例模式。
?
生成器模式(Builder)
1. 場(chǎng)景描述
在本模式中,我選用Java包里自帶的幾個(gè)類(lèi)做實(shí)例說(shuō)明,凡是用過(guò)Java、C#等高級(jí)編程語(yǔ)言的,都會(huì)知道有一個(gè)String類(lèi),而在Java中,如果要對(duì)String對(duì)象應(yīng)用“+”等操作效率是很低的,JDK中提供了一個(gè)抽象類(lèi)叫做AbstractStringBuilder,用這個(gè)類(lèi)來(lái)構(gòu)造復(fù)雜的字符串效率會(huì)高很多。可能大家對(duì)這個(gè)類(lèi)比較陌生,但是對(duì)它的兩個(gè)子類(lèi):StringBuilder和StringBuffer就比較熟悉了,它們用append方法來(lái)串聯(lián)字符串(當(dāng)然還提供有其他諸如insert的方法,先只使用用得最多的append來(lái)做例子,后面實(shí)現(xiàn)中會(huì)加上insert),最后用一個(gè)toString方法把生成的對(duì)象返回。
2. 生成器模式
生成器模式是將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離開(kāi),使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示,其實(shí)用AbstractStringBuilder做例子有一個(gè)不好的地方,那就是它的兩個(gè)子類(lèi)創(chuàng)建的是一種對(duì)象,對(duì)應(yīng)到上面的定義中,就是“構(gòu)建過(guò)程中創(chuàng)建了相同的表示”。
生成器模式要把構(gòu)建過(guò)程獨(dú)立出來(lái),成為一個(gè)指導(dǎo)者,它指導(dǎo)裝配,但不負(fù)責(zé)具體實(shí)現(xiàn),而負(fù)責(zé)具體實(shí)現(xiàn)的就是生成器,模式結(jié)構(gòu)圖如下:
目的是構(gòu)造Product對(duì)象,在具體生成器類(lèi)的buildPart方法中進(jìn)行生成,最后通過(guò)getResult方法返回,而在指導(dǎo)者內(nèi)部,就使用:
public class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}public void construct(){builder.buildPart();} }在生成String的例子中,實(shí)現(xiàn)類(lèi)中的append、insert方法就相當(dāng)于buildPart,toString方法相當(dāng)于getResult,對(duì)應(yīng)有以下結(jié)構(gòu)圖:
說(shuō)明:其實(shí)toString方法是在抽象類(lèi)AbstractStringBuilder中定義的抽象方法,本圖中放在子類(lèi)里,更接近前面的一般結(jié)構(gòu)圖。首先不管那個(gè)指導(dǎo)者(也可以認(rèn)為Client就是指導(dǎo)者),在具體的Builder實(shí)現(xiàn)類(lèi)中,每個(gè)構(gòu)建部件的方法都包含創(chuàng)建或組裝部件的功能,在這里String類(lèi)就是要構(gòu)建的復(fù)雜對(duì)象,下面看一下我簡(jiǎn)化了的AbstractStringBuilder類(lèi):
public abstract class AbstractStringBuilder {//實(shí)際上還實(shí)現(xiàn)了Appendable, CharSequence這兩個(gè)接口char[] value;int count;public AbstractStringBuilder append(String paramString){//做追加字符的操作return this;}public AbstractStringBuilder insert(int paramInt, String paramString){//做插入字符的操作return this;}public abstract String toString(); }以及其中一個(gè)子類(lèi)StringBuilder:
public class StringBuilder extends AbstractStringBuilder{//原類(lèi)中實(shí)現(xiàn)Serializable, CharSequence這兩個(gè)接口public StringBuilder append(String paramString){super.append(paramString);return this;}public StringBuilder insert(int paramInt, String paramString){super.insert(paramInt, paramString);return this;}@Overridepublic String toString() {// TODO Auto-generated method stubreturn new String(this.value, 0, this.count);} }除了append方法之外,我把insert方法也加了進(jìn)來(lái),可以注意到生成器的構(gòu)建部件的方法(如append)有一個(gè)不成文的特點(diǎn),就是會(huì)返回本類(lèi)的對(duì)象來(lái)供外部使用,而不是簡(jiǎn)單地void類(lèi)型。如果需要處理細(xì)節(jié),那么可以在toString方法中加一些約束。在客戶(hù)端的調(diào)用如下:
public class Client {public static void main(String[] args) {// TODO Auto-generated method stubAbstractStringBuilder builder = new StringBuilder();String s = builder.append("qwe").append("asd").insert(3, "zxc").toString();//s對(duì)象的其他操作 } }那么這樣實(shí)現(xiàn)有什么優(yōu)勢(shì)嗎,使用這個(gè)生成器,可以把生成String這個(gè)復(fù)雜對(duì)象的過(guò)程分成一個(gè)個(gè)的小部分來(lái)進(jìn)行構(gòu)建,而不是很混亂的在Client端去拼湊這個(gè)字符串更甚至是拼湊字符數(shù)組。
3. 帶指導(dǎo)者的String生成器
下面對(duì)上面的場(chǎng)景做一個(gè)改進(jìn),比方說(shuō),現(xiàn)在要求,在每一個(gè)生成的字符串末尾,需要添加一行說(shuō)明,用來(lái)描述是什么類(lèi)的對(duì)象(StringBuilder還是StringBuffer)生成的這個(gè)String,以及其他一些信息,既然每個(gè)要構(gòu)造的字符串都有正文和最后的描述這兩部分,那么還在Client里面去生成就不好了吧?那么這個(gè)任務(wù)就交由指導(dǎo)者去完成,首先為每個(gè)Builder的實(shí)現(xiàn)類(lèi)添加一個(gè)appendTag方法來(lái)附加尾部信息,然后創(chuàng)建指導(dǎo)者類(lèi)Director:
public class Director {private AbstractStringBuilder builder;public Director(AbstractStringBuilder builder) {this.builder = builder;}public void construct(Map<Integer, String> param){for(Integer i : param.keySet()){if(i < 0)builder.append(param.get(i));elsebuilder.insert(i, param.get(i));}builder.appendTag();String s = builder.toString();} }傳入的Map為一個(gè)整數(shù)和字符串的對(duì)應(yīng),整數(shù)小于0表示追加,大于等于0表示在相應(yīng)位置插入。而在客戶(hù)端只需要按照需求創(chuàng)建一個(gè)StringBuilder或是StringBuffer的對(duì)象,傳入?yún)?shù)就可以了:
public class Client {public static void main(String[] args) {// TODO Auto-generated method stubAbstractStringBuilder builder = new StringBuilder();//AbstractStringBuilder builder = new StringBuffer();Director d = new Director(builder);Map<Integer, String> param = new HashMap<Integer, String>();param.put(-1, "qwe");param.put(-1, "asd");param.put(3, "zxc");d.construct(param);} }4. 模式說(shuō)明
生成器模式的主要功能是分步驟構(gòu)建復(fù)雜的產(chǎn)品,重在一步一步解決構(gòu)造復(fù)雜對(duì)象的問(wèn)題,而且更為重要的是,這個(gè)構(gòu)造過(guò)程是一致的,具體實(shí)現(xiàn)方式可能會(huì)不同,這些不同體現(xiàn)在具體生成器里,在指導(dǎo)者中,根據(jù)不同的生成器,使用相同的構(gòu)建過(guò)程,就能創(chuàng)建出不同的產(chǎn)品(String的例子中創(chuàng)建的是相同的產(chǎn)品,如果StringBuilder和StringBuffer分別生成String的一個(gè)子類(lèi)對(duì)象,那就是一般化的生成器實(shí)現(xiàn)了,而且,其實(shí)標(biāo)準(zhǔn)生成器模式也不需要給這些千差萬(wàn)別的產(chǎn)品提供一個(gè)統(tǒng)一的接口)。
所以說(shuō),生成器主要有兩部分:Builder和Director,Builder來(lái)進(jìn)行部件構(gòu)建和產(chǎn)品裝配,Director來(lái)進(jìn)行整體構(gòu)建,Builder是變化的,而Director的構(gòu)建算法是固定的。可以在客戶(hù)端創(chuàng)造Director來(lái)進(jìn)行構(gòu)造,簡(jiǎn)單情況下也可以直接讓客戶(hù)端來(lái)充當(dāng)Director。
再注意一點(diǎn),標(biāo)準(zhǔn)的生成器模式,Builder中返回最終產(chǎn)品的方法,應(yīng)該是聲明在實(shí)現(xiàn)里而不是接口上,這種考慮是想讓Director只負(fù)責(zé)構(gòu)建,甚至返回最終產(chǎn)品都不用Director來(lái)管,那么在哪得到最終產(chǎn)品呢?在客戶(hù)端,因?yàn)橹挥锌蛻?hù)端知道它創(chuàng)建的是哪一種實(shí)現(xiàn),只有客戶(hù)端才能調(diào)用實(shí)現(xiàn)里有但是接口里沒(méi)有的行為,具體來(lái)說(shuō),在客戶(hù)端:
StringBuffer builder = new StringBuffer(); Director d = new Director(builder); d.construct(new HashMap<Integer, String>()); String s = builder.toString();使用生成器模式,可以使耦合更加松散,輕易地修改實(shí)現(xiàn)(Builder內(nèi)部的裝配過(guò)程),構(gòu)建算法與具體實(shí)現(xiàn)相分離,使代碼具有更好的復(fù)用性。
?
原型模式(Prototype)
1. 場(chǎng)景描述
StringBuilder和StringBuffer雖然很接近,但是也有應(yīng)用場(chǎng)景的區(qū)別,StringBuilder更適用于單線程復(fù)雜對(duì)象的構(gòu)建,而StringBuffer更適用于多線程并發(fā)訪問(wèn)時(shí)字符串的構(gòu)建,現(xiàn)在需求來(lái)了,有這么一個(gè)模塊,負(fù)責(zé)接收一個(gè)半成品的AbstractStringBuilder對(duì)象,復(fù)制多份,稍作修飾后分發(fā)給不同模塊做進(jìn)一步修飾,那應(yīng)該怎么實(shí)現(xiàn)呢?可能一下就會(huì)想到,這很簡(jiǎn)單,創(chuàng)建幾個(gè)新的AbstractStringBuilder實(shí)例,把對(duì)應(yīng)字段都賦值到新創(chuàng)建的對(duì)象上,然后把這新創(chuàng)建的對(duì)象挨個(gè)傳給對(duì)應(yīng)模塊就行了。但是,外部選用StringBuilder還是StringBuffer來(lái)實(shí)現(xiàn)是有它的道理的,我們不應(yīng)該去改變這個(gè)選擇,那么問(wèn)題就來(lái)了,不知道外部用的到底是哪個(gè)類(lèi),怎么創(chuàng)建新對(duì)象?
注:本章只是仍選用AbstractStringBuilder做例子,但已經(jīng)和JDK中本來(lái)的實(shí)現(xiàn)沒(méi)有多大關(guān)系了。
2. 耦合度較高的實(shí)現(xiàn)
一種實(shí)現(xiàn)方法是使用instanceof關(guān)鍵詞來(lái)判斷傳入的到底是哪種類(lèi)型,根據(jù)不同的判斷結(jié)果創(chuàng)建不同的對(duì)象:
public void forward(AbstractStringBuilder s){AbstractStringBuilder newString = null;if(s instanceof StringBuilder){newString = new StringBuilder();//將s中響應(yīng)字段的值賦值給newString }else if(s instanceof StringBuffer){newString = new StringBuffer();//將s中響應(yīng)字段的值賦值給newString }//對(duì)newString做修飾//將newString分發(fā)給特定模塊 }可想而知,這樣的實(shí)現(xiàn)方式耦合度是非常高的,一來(lái)作為這個(gè)分發(fā)模塊,僅僅需要知道我要復(fù)制一個(gè)AbstractStringBuilder對(duì)象,并調(diào)用特定方法進(jìn)行修飾分發(fā)就可以了,還需要知道具體實(shí)現(xiàn)嗎,而且這樣也很不利于擴(kuò)展。
那么更好地實(shí)現(xiàn)是怎么樣的呢?forward這個(gè)模塊是不知道傳進(jìn)來(lái)的對(duì)象到底是哪種類(lèi)型的,但是傳進(jìn)來(lái)的對(duì)象本身是知道的,所以可以通過(guò)這個(gè)“原型實(shí)例”來(lái)創(chuàng)建新的對(duì)象。
3. 原型模式
原型模式,通俗地講就是在每個(gè)類(lèi)的內(nèi)部提供一個(gè)創(chuàng)建新對(duì)象的方法,并與this對(duì)象有相同的值。換言之,原型模式要求對(duì)象實(shí)現(xiàn)一個(gè)可以克隆自身的接口,調(diào)用這個(gè)方法就可以通過(guò)拷貝或者克隆自身來(lái)創(chuàng)建一個(gè)新對(duì)象。
基本結(jié)構(gòu)如下:
在operating方法中,就可以通過(guò)Prototype p = prototype.clone();來(lái)創(chuàng)建新Prototype實(shí)例,為了要求每個(gè)子類(lèi)(實(shí)現(xiàn)類(lèi))都指定自己的克隆實(shí)現(xiàn),要在公共接口中定義該方法,或在抽象類(lèi)中加入clone的抽象方法。在標(biāo)準(zhǔn)的原型實(shí)現(xiàn)中,客戶(hù)端是持有待克隆的對(duì)象的,就像上面結(jié)構(gòu)圖所描述的,但在這里的例子中,改用傳參的方式傳入待克隆對(duì)象,思想是差不多的。
現(xiàn)在在AbstractStringBuilder抽象類(lèi)中添加該抽象方法cloneAsb(因?yàn)镴ava的Object類(lèi)里是自帶clone方法的,這里改個(gè)名字以示區(qū)分),在子類(lèi)(實(shí)現(xiàn)類(lèi))覆寫(xiě)如下,這里給出StringBuilder中的實(shí)現(xiàn),StringBuffer中同理:
@Override public AbstractStringBuilder cloneAsb() {// TODO Auto-generated method stubStringBuilder builder = new StringBuilder();builder.count = this.count;builder.value = new char[this.value.length];for(int i = 0 ; i < this.value.length ; i++)builder.value[i] = this.value[i];return builder; }克隆出來(lái)的對(duì)象通常不僅僅是new出來(lái)的新對(duì)象,而是有值的,并且不同于簡(jiǎn)單的在clone方法中返回this,對(duì)克隆出來(lái)的新實(shí)例的修改,不影響原實(shí)例的值。
4. Java中的克隆方法
Java在Object類(lèi)中是實(shí)現(xiàn)了clone方法的,訪問(wèn)權(quán)限為protected,我們可以不在接口或抽象類(lèi)中定義自己的克隆方法,而是直接覆寫(xiě)該方法并調(diào)用父類(lèi)實(shí)現(xiàn)來(lái)進(jìn)行克隆,這里直接讓實(shí)現(xiàn)類(lèi)去實(shí)現(xiàn)Cloneable接口(這是一個(gè)標(biāo)識(shí)接口,里面沒(méi)有內(nèi)容),并在子類(lèi)中繼承Object的該方法如下:
public Object clone() throws CloneNotSupportedException {// TODO Auto-generated method stubreturn super.clone(); }這樣的實(shí)現(xiàn)有一個(gè)問(wèn)題,就是這樣的克隆方法是淺克隆,只克隆值傳遞的數(shù)據(jù),而引用類(lèi)型的數(shù)據(jù)克隆之后還是原引用,指向的內(nèi)存空間是一樣的,所以對(duì)于引用型的變量,要再去實(shí)現(xiàn)它的clone方法,并在當(dāng)前的克隆方法中一一進(jìn)行賦值:
public Object clone() throws CloneNotSupportedException {// TODO Auto-generated method stubObject obj = super.clone();obj.setInnerObject(this.innerObject.clone());return obj; }5. 模式說(shuō)明
有時(shí)候還會(huì)創(chuàng)建一個(gè)原型管理器來(lái)管理可被克隆的原型,其實(shí)就是有一個(gè)Map型的變量,所有原型在第一次創(chuàng)建后存入到這個(gè)Map變量當(dāng)中,以后再要?jiǎng)?chuàng)建該類(lèi)的實(shí)例就直接向原型管理器要原型。原型可以被添加、注銷(xiāo)或獲取:
public class PrototypeManager {private static Map<String, Prototype> map = new HashMap<String, Prototype>();//主要的方法通過(guò)靜態(tài)訪問(wèn)就可以了,不需要讓外部創(chuàng)建對(duì)象private PrototypeManager(){}//注冊(cè)原型public synchronized static void setPrototype(String pid, Prototype p){map.put(pid, p);}//銷(xiāo)毀注冊(cè)public synchronized static void removePrototype(String pid){map.remove(pid);}//獲取原型public synchronized static Prototype getPrototype(String pid) throws Exception{Prototype p = map.get(pid);if(p == null)throw new Exception("該原型未注冊(cè)或已被銷(xiāo)毀!");return p;} }原型模式的優(yōu)點(diǎn)就是隱藏具體實(shí)現(xiàn)類(lèi)型,不用使用類(lèi)似instanceof的判斷來(lái)分情況討論,更不會(huì)錯(cuò)誤地改變實(shí)現(xiàn)類(lèi)型,但是必須要求原型實(shí)現(xiàn)clone方法,尤其在引用型變量很多的情況下會(huì)很麻煩。
?
轉(zhuǎn)載于:https://www.cnblogs.com/smarterplanet/archive/2012/10/13/2722429.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專(zhuān)家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的【设计模式】单例模式-生成器模式-原型模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 语法:MySQL中INSERT INTO
- 下一篇: 10-10数组的介绍