(九)模板方法模式詳解(包含與類加載器不得不說的故事)

?作者:zuoxiaolong8810(左瀟龍),轉載請注明出處,特別說明:本博文來自博主原博客,為保證新博客中博文的完整性,特復制到此留存,如需轉載請注明新博客地址即可。

????????????????? 模板方法模式,這是一個在許多優秀的開源項目中LZ見的最多的一個設計模式,也是LZ覺得最為優秀的一個設計模式,所以這一章LZ會盡自己所能的去盡量將這個設計模式解釋清楚。

????????????????? 模板方法模式,一般是為了統一子類的算法實現步驟,所使用的一種手段或者說是方式。它在父類中定義一系列算法的步驟,而將具體的實現都推遲到子類。

????????????????? 最典型的形式就是一個接口,一個抽象父類,父類中會有一系列的抽象方法,而在子類中去一一實現這些方法。

????????????????? 下面LZ給舉一個例子,比如我們有一個接口,里面就一個方法,是用來制造一個HTML頁面,如下。

public interface PageBuilder {String bulidHtml();}

???????????????? 這個接口很簡單,就是直接制造一個Html頁面的內容,假設我們不使用模板方法模式,直接讓各個子類去直接實現這個接口,那么肯定實現的方式千奇百怪,而且步驟也亂七八糟的,這樣實在不利于維護和擴展。所以我們可以使用模板方法模式,將這個過程給制定好,然后把具體的內容填充交給子類就好,這樣這些子類生成的HTML頁面就會非常一致。

???????????????? 基于這個目的,我們定義如下抽象類,去實現這個接口,并且我們定義好步驟。

復制代碼
public abstract class AbstractPageBuilder implements PageBuilder{private StringBuffer stringBuffer = new StringBuffer();public String bulidHtml() {//首先加入doctype,因為都是html頁面,所以我們父類不需要推遲給子類實現,直接在父類實現stringBuffer.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");//頁面下面就是成對的一個HTML標簽,我們也在父類加入,不需要給子類實現stringBuffer.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">");//下面就應該是head標簽里的內容了,這個我們父類做不了主了,推遲到子類實現,所以我們定義一個抽象方法,讓子類必須實現
        appendHead(stringBuffer);//下面是body的內容了,我們父類依然無法做主,仍然推遲到子類實現
        appendBody(stringBuffer);//html標簽的關閉stringBuffer.append("</html>");return stringBuffer.toString();}//第一個模板方法protected abstract void appendHead(StringBuffer stringBuffer);//第二個模板方法protected abstract void appendBody(StringBuffer stringBuffer);}
復制代碼

? ? ? ? ? ? ? 上面LZ已經加了注釋,這下我們如果要制作一個html頁面,就直接繼承我們的抽象父類就可以了,而我們的子類只需要實現兩個模板方法,就可以成功完成html頁面的創建,下面LZ給出一個子類,我們隨意制造一個html頁面。

復制代碼
public class MyPageBuilder extends AbstractPageBuilder{@Overrideprotected void appendHead(StringBuffer stringBuffer) {stringBuffer.append("<head><title>你好</title></head>");}@Overrideprotected void appendBody(StringBuffer stringBuffer) {stringBuffer.append("<body><h1>你好,世界!</h1></body>");}public static void main(String[] args) {PageBuilder pageBuilder = new MyPageBuilder();System.out.println(pageBuilder.bulidHtml());}}
復制代碼

? ? ? ? ? ? ? ?我們簡單的加入一個head和body標簽,然后創建測試類運行一下,就會發現,我們按照父類給的標準模板,生成了一個html頁面。

? ? ? ? ? ? ? ?這樣做的方式的好處是,父類可以規范子類的創建過程,便于我們維護,而且子類也更省事,因為像doctype包括html標簽都是一樣的,所以子類不再需要關心這些。當然上述LZ寫的有點粗糙,其實我們可以定義的更仔細一點,比如head標簽里,第一個是title,然后是meta等等。但作為例子,我們還是遵循簡單的原則,主要還是想給各位傳達模板方法模式的思想。

? ? ? ? ? ? ? ?模板方法模式是所有設計模式當中,LZ覺得最無侵入性的模式,因為它的好處實在是太明顯了。模板方法模式并不強制接口的實現類必須繼承,所以不會對子類造成任何影響,而如果子類的實現可以配得上模板類的模板,那么就可以享受模板方法模式帶來的好處。

???????????????通常情況下,模板方法模式用于定義構建某個對象的步驟與順序,或者定義一個算法的骨架。

???????????????我們剛才的示例明顯就是構建一個String對象的過程,在這里要聲明一點,對于模板方法模式,父類提供的構建步驟和順序或者算法骨架,通常是不希望甚至是不允許子類去覆蓋的,所以在某些場景中,可以直接將父類中提供骨架的方法聲明為final類型。

? ? ? ? ? ? ? ?模板方法模式還有一種使用的方式,為了給子類足夠的自由度,可以提供一些方法供子類覆蓋,去實現一些骨架中不是必須但卻可以有自定義實現的步驟。

? ? ? ? ? ? ? ?比如上述的例子當中,我們應該都知道,HTML頁面中有一些標簽是可有可無的。比如meta標簽,link標簽,script標簽等。那么我們可以將剛才的例子細化一下,去看一下上面說的供子類覆蓋的方法是什么。我們將剛才的抽象父類細化成如下形式。

復制代碼
public abstract class AbstractPageBuilder implements PageBuilder{private static final String DEFAULT_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";private static final String DEFAULT_XMLNS = "http://www.w3.org/1999/xhtml";private StringBuffer stringBuffer = new StringBuffer();public String bulidHtml() {stringBuffer.append(DEFAULT_DOCTYPE);stringBuffer.append("<html xmlns=\"" + DEFAULT_XMLNS + "\">");stringBuffer.append("<head>");appendTitle(stringBuffer);appendMeta(stringBuffer);appendLink(stringBuffer);appendScript(stringBuffer);stringBuffer.append("</head>");appendBody(stringBuffer);stringBuffer.append("</html>");return stringBuffer.toString();}protected void appendMeta(StringBuffer stringBuffer){}protected void appendLink(StringBuffer stringBuffer){}protected void appendScript(StringBuffer stringBuffer){}protected abstract void appendTitle(StringBuffer stringBuffer);protected abstract void appendBody(StringBuffer stringBuffer);}
復制代碼

????????????????可以看到,我們將head標簽的生成過程更加細化了,分成四個方法,title,meta,link和script。但是這四個里面appendTitle是模板方法,子類必須實現,而其它三個則是普通的空方法。

??????????????? 那么上述三個方法,就是留給子類覆蓋的,當然子類可以選擇不覆蓋,那么生成的HTML就沒有meta,link和script這三種標簽,如果想有的話,就可以覆蓋其中任意一個,比如下面這樣。

復制代碼
public class MyPageBuilder extends AbstractPageBuilder{protected void appendMeta(StringBuffer stringBuffer) {stringBuffer.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />");}protected void appendTitle(StringBuffer stringBuffer) {stringBuffer.append("<title>你好</title>");}protected void appendBody(StringBuffer stringBuffer) {stringBuffer.append("<body>你好,世界!</body>");}public static void main(String[] args) {PageBuilder pageBuilder = new MyPageBuilder();System.out.println(pageBuilder.bulidHtml());}}
復制代碼

??????????????? 我們覆蓋了appendMeta方法,所以我們就可以在head標簽中生成一個meta標簽。如果各位看過上章的適配器模式,其實這里和缺省適配很像,目的都是一樣的,因為如果把appendMeta也寫成抽象方法,那么子類就必須實現,但是meta標簽又不是必須的,所以子類就有可能把appendMeta,appendLink,appendScript方法全空著了。

????????????????所以為了不強制子類實現不必要的抽象方法,但又不剝奪子類自由選擇的權利,我們在父類提供一個默認的空實現,來讓子類自由選擇是否要覆蓋掉這些方法。

??????????????? 說到模板方法模式,我們JDK當中有一個類與它還有一個不得不說的故事,那就是類加載器。

??????????????? JDK類加載器可以大致分為三類,分別是啟動類加載器,擴展類加載器,以及應用程序加載器。

????????????????這三者加載類的路徑分別為如下:

????????????????啟動類加載器:JAVA_HOME/lib目錄下,以及被-Xbootcalsspath參數設定的路徑,不過啟動類加載器加載的類是有限制的,如果JVM不認識的話,你放在這些目錄下也沒用。

????????????????擴展類加載器:JAVA_HOME/lib/ext目錄下,以及被java.ext.dirs系統變量指定的路徑。

????????????????應用程序類加載器:用戶自己的類路徑(classpath),這個類加載器就是我們經常使用的系統類加載器,并且JDK中的抽象類ClassLoader的默認父類加載器就是它。

??????????????? 在這里為什么說類加載器和模板方法模式有關呢,是因為ClassLoader類就使用了模板模式,去保證類加載過程中的唯一性。LZ先給各位看下這個類當中的模板模式的應用。

復制代碼
public abstract class ClassLoader {//這是一個重載方法public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}//這里就是父類算法的定義protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{Class c = findLoadedClass(name);if (c == null) {try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClass0(name);}} catch (ClassNotFoundException e) {c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}//這里留了一個方法給子類選擇性覆蓋protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}}
復制代碼

????????????? LZ截取了主要的部分,為了突出這三個方法。在上面LZ加了簡單的注釋,相信經過剛才的介紹,各位應該能看出來這是一個模板方法模式,只是它沒有定義抽象方法,因為findClass這個方法,并不是必須實現的,所以JDK選擇留給程序員們自己選擇是否要覆蓋。

??????????????從代碼上我們可以看出,在ClassLoader中定義的算法順序是。

????????????? 1,首先看是否有已經加載好的類。

????????????? 2,如果父類加載器不為空,則首先從父類類加載器加載。

????????????? 3,如果父類加載器為空,則嘗試從啟動加載器加載。

????????????? 4,如果兩者都失敗,才嘗試從findClass方法加載。

??????????????這是JDK類加載器的雙親委派模型,即先從父類加載器加載,直到繼承體系的頂層,否則才會采用當前的類加載器加載。這樣做的目的剛才已經說了,是為了JVM中類的一致性。????????????????????????????

??????????????如果有讀者第一次接觸這方面的知識,估計會比較迷茫,下面LZ給出一個例子。各位猜測下下面程序的運行結果會是什么?

復制代碼
package com.classloader;public class ClassLoaderTest {public static void main(String[] args) throws Exception {Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("com.classloader.ClassLoaderTest");Object entity = clazz.newInstance();System.out.println(entity instanceof ClassLoaderTest);}
}
復制代碼

??????????????相信各位都可以毫無疑問的猜測出來,結果應該是true,這是因為entity是ClassLoaderTest類的一個實例,instanceof關鍵字用來判斷一個實例是否屬于一個特定的類型,所以結果就是true。

??????????????那么各位再來猜猜下面這段代碼的運行結果會是什么?

復制代碼
package com.classloader;import java.io.IOException;
import java.io.InputStream;class MyClassLoader extends ClassLoader{public Class<?> loadClass(String name) throws ClassNotFoundException {String fileName = name.substring(name.lastIndexOf(".")+1) + ".class";InputStream is = getClass().getResourceAsStream(fileName);if (is == null) {return super.loadClass(name);}try {byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {throw new ClassNotFoundException();}}}public class ClassLoaderTest {public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {ClassLoader classLoader = new MyClassLoader();Class<?> clazz = classLoader.loadClass("com.classloader.ClassLoaderTest");Object entity = clazz.newInstance();System.out.println(entity instanceof ClassLoaderTest);}}
復制代碼

??????????????對于類加載器比較熟悉的讀者們可能覺得這個結果并不出乎意料,可是或許還是有人會比較意外,為什么結果會是false呢?

????????????? 這是因為如果沒有按照ClassLoader中提供的骨架算法去加載類的話,可能會造成JVM中有兩個一模一樣的類信息,他們是來自一個類文件,但卻不是一個加載器加載的,所以這兩個類不相等。

????????????? 這也是類加載器為何要使用模板模式給我們定義好查找的算法,是為了保證我們加載的每一個類在虛擬機當中都有且僅有一個。

????????????? 不過你可能會想,既然如此,為何不把loadClass方法寫成final類型的,這樣不是更安全嗎?

????????????? 這是因為有的時候我們希望JVM當中每一個類有且僅有一個,但有的時候我們希望有兩個,甚至N個,就比如我們的tomcat,你可以想象下,你每一個項目假設都有com.xxx.xxxx.BaseDao等等,如果這些類都是一個的話,你的tomcat還能同時啟動多個WEB服務嗎?雖說tomcat也是遵循的雙親委派模型,但是從此也可以看出來,我們并不是在所有時候都希望同一個全限定名的類在整個JVM里面只有一個。

????????????? 這里提到類加載器,是為了給模板方法一個現有的現實中的例子,以便于有些看多了自己制造的例子的讀者可以換個口味,如果有機會,LZ會在這個系列完結以后,專門開一個系列來和各位分享學習虛擬機過程中的感悟,本次不再過多介紹類加載器的相關內容。

????????????? 另外,如果多掌握一些類加載器的知識,還是對平時的工作和學習有很大幫助的,各位也可以私下去搜索下相關資料。

??????????????好了,模板方法模式就介紹到這里吧,希望各位都有自己的收獲。

????????????? 謝謝觀看。

??????????????下期預告,裝飾器模式。
?

??????????????????????

?????????????????????


版權聲明


作者:zuoxiaolong(左瀟龍)

出處:博客園左瀟龍的技術博客--http://www.cnblogs.com/zuoxiaolong

您的支持是對博主最大的鼓勵,感謝您的認真閱讀。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

轉載于:https://www.cnblogs.com/Zyf2016/p/6337740.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/255751.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/255751.shtml
英文地址,請注明出處:http://en.pswp.cn/news/255751.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

阿里云openapi接口使用,PHP,視頻直播

1.下載sdk放入項目文件夾中 核心就是aliyun-php-sdk-core&#xff0c;它的配置文件會自動加載相應的類 2.引入文件 include_once LIB_PATH . ORG/aliyun-openapi/aliyun-php-sdk-core/Config.php; 3.配置客戶端對象,需要Access Key ID,Access Key Secret $iClientProfile Defa…

【面經——《廣州敏視數碼科技有限公司》——圖像處理算法工程師-深度學習方向】

目錄 筆試 HR面 專業面——60多分鐘 主管面 反問&#xff1a; 筆試 8道題——簡答題 1道編程 蘋果、香蕉、梨、菠蘿&#xff0c;彩色圖像如何進行分類&#xff1f;一輛帶車牌的汽車&#xff0c;圖像亮度整體呈現偏亮狀態&#xff0c;如何…

Android之網絡編程利用PHP操作MySql插入數據(四)

因為最近在更新我的項目&#xff0c;就想著把自己在項目中用到的一些的簡單的與網絡交互的方法總結一下&#xff0c;所以最近Android網絡編程方面的博文會比較多一些&#xff0c;我盡量以最簡單的方法給大家分享&#xff0c;讓大家明白易懂。如果有什么不對的地方&#xff0c;還…

RAPID 信號的互鎖和同步 WaitTestAndSet 和 TestAndSet

RAPID 信號的互鎖和同步 WaitTestAndSet 指令等待指定的持久型 BOOL 變量變成 FALSE.當變量值變為 FALSE, 該指令將設置變量為 TRUE 并繼續執行. 該持久型變量可被作為同步或者互斥時的一個 BOOL 信號量。 這個指令與 TestAndSet 有著同樣的基本功能。但是 WaitTestAnd…

【常用網址】——opencv等

opencv官網Releases - OpenCVhttps://opencv.org/releases/

(五):C++分布式實時應用框架——微服務架構的演進

C分布式實時應用框架——微服務架構的演進 技術交流合作QQ群&#xff1a;436466587 歡迎討論交流 上一篇&#xff1a;(四)&#xff1a;C分布式實時應用框架——狀態中心模塊 版權聲明:本文版權及所用技術歸屬smartguys團隊所有&#xff0c;對于抄襲&#xff0c;非經同意轉載等…

如何通過軟件項目開發來提高自身的實力。

在我們這個專業&#xff0c;大多數人都不會將軟件開發當作自己的事業&#xff0c;因為若要在這個行業上能夠立足&#xff0c;得需要一個好的基礎&#xff0c;但是由于這個東西并不是可以通過書本能夠徹底的理解和 掌握的&#xff0c;隨著時間的變化&#xff0c;我們身邊的科技也…

夢回JavaScript--數據類型之undefined

undefined類型只有一個值&#xff0c;即undefined。在使用var聲明變量但未對其加以初始化時&#xff0c;這個變量的值就是undefined&#xff1b; var mes; alert(mes undefined) //true如果變量沒有聲明就會出現錯誤 var mes; alert(mes) //undefined alert(a)//error 然而有一…

Robot Application Builder

軟件開發工具包 Robot Application Builder是安裝在PC機&#xff08;Windows 2000或Windows XP操作系統&#xff09;上的一種獨立開發工具&#xff0c;可用于創建運行于ABB FlexPendant示教器或PC機上的定制化操作界面。為此&#xff0c;該軟件包由以下兩部分組成&#xff1a;…

asp.net model 驗證和取出 ErrorMessage 信息

為什么80%的碼農都做不了架構師&#xff1f;>>> public class Users{public int Id { get; set; }public string Name { get; set; }[Required(ErrorMessage "郵箱不能為空")][EmailAddressAttribute(ErrorMessage "郵箱格式不正確")]public…

this

作者&#xff1a;李挺鏈接&#xff1a;https://www.zhihu.com/question/19636194/answer/123274198來源&#xff1a;知乎著作權歸作者所有&#xff0c;轉載請聯系作者獲得授權。關于 this 的描述&#xff0c;曾經在 stackoverflow 上看到了一篇回答寫的非常詳盡&#xff0c;下面…

DeviceNet 消息類型

DeviceNet是一種低成本的通訊總線鏈接&#xff0c;具有開放現場網絡標準&#xff0c;規范和協議都是開放的。DeviceNet將控制和數據融合在一起&#xff0c;信息具有數據標識區&#xff0c;網絡利用標識區進行優先級仲裁&#xff0c;可以高效傳送I/O數據。 DeviceNet有兩種不同類…

【pyqt5學習——信號與槽】實例計時器(解決界面卡頓問題)

目錄 一、方法一&#xff1a;另開線程 1、什么是信號與槽 1&#xff09;GUI控件&#xff08;信號&#xff09;與槽 2&#xff09;自定義信號與槽 2、實戰1&#xff1a;計時器&#xff08;不自定義信號槽和不使用多線程&#xff09; 1&#xff09;界面設計——利用qt-desi…

【轉】為什么螺絲都是六角的?

6邊形的螺絲擰60度就可以圖形還原&#xff08;不知道表述清楚沒&#xff0c;見討論中的解釋&#xff09;&#xff09; 如果空間比較狹小&#xff0c;只要扳手能擰動60度就能安裝上螺絲 這是在擰動角度和邊長相互妥協后的產物 試想 如果是正方形&#xff0c;邊長夠長了&#xff…

用PHP和Websocket實現實時通訊

說到websocket大家一定不會陌生&#xff0c;WebSocket是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信(full-duplex)。一開始的握手需要借助HTTP請求完成&#xff0c;當瀏覽器和服務器握手成功后&#xff0c;瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可…

ABB SocketReceive 套接口 函數

SocketReceive函數使用方法&#xff1a; SocketReceive 從遠程計算機接收數據。 SocketReceive 可以被客戶端和服務器程序使用。 基本樣例&#xff1a; 下述樣例說明了 SocketReceive 的語法: Example 1 VAR string str_data; ... SocketRe…

【數據庫學習筆記】——創建數據庫連接對象connection

目錄 connect函數的參數 創建連接對象連接MySQL代碼 連接對象常見屬性與方法 事務名詞解釋 課程視頻鏈接&#xff1a; 第14節 Python操作數據庫_嗶哩嗶哩_bilibili666https://www.bilibili.com/video/BV1q54y147KX?fromsearch&seid968950907021994347&spm_id_from3…

數據庫常用增刪改查記錄等語句

1增 1.1【插入單行】insert [into] <表名> (列名) values (列值)例&#xff1a;insert into Strdents (姓名,性別,出生日期) values (開心朋朋,男,1980/6/15) 1.2【將現有表數據添加到一個已有表】insert into <已有的新表> (列名) select <原表列名> from &…

一個關于pynoi游戲的C語言編程

“去吧&#xff0c;秦&#xff0c;好好享受這個夜晚&#xff0c;我給你準備了一份禮物&#xff0c;希望你能喜歡。”小布萊克眨著眼睛笑道&#xff0c;狡猾的像一頭小狐貍。轉載于:https://www.cnblogs.com/jackey18/p/8260774.html