1 認識設計模式
1.1 設計模式簡介
軟件設計模式(Software Design Pattern),俗稱設計模式,設計模式是一套被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結。它描述了在軟件設計過程中的一些不斷重復發生的問題,以及該問題的解決方案。也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具有一定的普遍性,可以反復使用。使用設計模式的目的是為了代碼重用、讓代碼更容易被他人理解、保證代碼可靠性。
1.2 設計原則
優良的系統設計具備特點:
- 可擴展性(Extensibility)
- 靈活性(Flexibility)
- 組件化可插拔式(Pluggability)
面向對象編程常用的設計原則包括7個,這些原則并不是孤立存在的,它們相互依賴,相互補充。
1.2.1 單一職責原則
定義:一個對象應該只包含單一的職責,并且該職責被完整地封裝在一個類中。
解說:一個類(或者大到模塊,小到方法)承擔的職責越多,它被復用的可能性越小,而且如果一個類承擔的職責過多,就相當于將這些職責耦合在一起,當其中一個職責變化時,可能會影響其他職責的運作。
類的職責主要包括兩個方面:數據職責和行為職責,數據職責通過其屬性來體現,而行為職責通過其方法來體現。
單一職責原則是實現高內聚、低耦合的指導方針,在很多代碼重構方法中都能找到它的存在,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責并將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關重構經驗。
實例:以登錄實現為例:
原始設計方案:
使用單一職責原則對其進行重構:
1.2.2 開閉原則
定義:一個軟件實體應當對擴展開放,對修改關閉。也就是說在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展,即實現在不修改源代碼的情況下改變這個模塊的行為。
解說:開閉原則還可以通過一個更加具體的“對可變性封裝原則”來描述,對可變性封裝原則(EVP)要求找到系統的可變因素并將其封裝起來。
如果一個軟件設計符合開閉原則,那么可以非常方便地對系統進行擴展,而且在擴展時無須修改現有代碼,使得軟件系統在擁有適應性和靈活性的同時具備較好的穩定性和延續性。為了滿足開閉原則,需要對系統進行抽象化設計,抽象化是開閉原則的關鍵。
實例:我們拿報表功能來說, BarChart 和 PieChart 為不同的報表功能,此時在 ChartDisplay 中使用報表功能,可以直接new對應的功能,但如果增加新的報表功能,在 ChartDisplay 中使用,就需要改代碼了,這就違背了開閉原則。
原始設計方案:
基于開閉原則進行重構:
1.2.3 里氏代換原則
定義:所有引用基類(父類)的地方必須能透明地使用其子類的對象。
解說:里氏代換原則可以通俗表述為:在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任 何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不 一定能夠使用基類對象。
里氏代換原則是實現開閉原則的重要方式之一,由于使用基類對象的地方都可以使用子類對 象,因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用 子類對象來替換父類對象。
實例:我們以給客戶發消息為例,給VIP客戶(VipCustomer)和普通客戶(CommonCustomer)發消息,在SendMessage 中分別定義給普通會員和VIP發消息,如果以后有新的客戶分類,不僅要添加客戶分類,還要修改SendMessage ,違背了開閉原則。
原始設計方案:
基于里氏代換原則進行重構:
1.2.4 依賴倒轉原則
定義:抽象不應該依賴于細節,細節應當依賴于抽象。換言之,要針對接口編程,而不是針對實現編程。
實例:我們可以把之前的開閉原則案例修改一下,利用Spring框架進行修改,可讀性更強,同時遵循了開閉原則、里氏代換原則和依賴倒轉原則,如下圖:
1.2.5 接口隔離原則
定義:使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。
講解:接口僅僅提供客戶端 需要的行為,客戶端不需要的行為則隱藏起來,應當為客戶端提供盡可能小的單獨的接口, 而不要提供大的總接口。在面向對象編程語言中,實現一個接口就需要實現該接口中定義的 所有方法,因此大的總接口使用起來不一定很方便,為了使接口的職責單一,需要將大接口 中的方法根據其職責不同分別放在不同的小接口中,以確保每個接口使用起來都較為方便, 并都承擔某一單一角色。接口應該盡量細化,同時接口中的方法應該盡量少,每個接口中只 包含一個客戶端(如子模塊或業務邏輯類)所需的方法即可,這種機制也稱為“定制服務”,即 為不同的客戶端提供寬窄不同的接口
實例:下圖展示了一個擁有多個客戶類的系統,在系統中定義了一個巨大的接口DataRead來服務所有的客戶類。
原始設計方案:
、
基于接口隔離原則進行重構:
1.2.6 合成復用原則
定義:盡量使用對象組合,而不是繼承來達到復用的目的
講解:合成復用原則就是在一個新的對象里通過關聯關系(包括組合關系和聚合關系)來使用一些 已有的對象,使之成為新對象的一部分;新對象通過委派調用已有對象的方法達到復用功能 的目的。簡言之:復用時要盡量使用組合/聚合關系(關聯關系),少用繼承。
在面向對象設計中,可以通過兩種方法在不同的環境中復用已有的設計和實現,即通過組合/ 聚合關系或通過繼承,但首先應該考慮使用組合/聚合,組合/聚合可以使系統更加靈活,降低 類與類之間的耦合度,一個類的變化對其他類造成的影響相對較少;其次才考慮繼承,在使 用繼承時,需要嚴格遵循里氏代換原則,有效使用繼承會有助于對問題的理解,降低復雜 度,而濫用繼承反而會增加系統構建和維護的難度以及系統的復雜度,因此需要慎重使用繼 承復用。
通過繼承來進行復用的主要問題在于繼承復用會破壞系統的封裝性,因為繼承會將基類的實 現細節暴露給子
類,由于基類的內部細節通常對子類來說是可見的,所以這種復用又稱“白 箱”復用,如果基類發生改變,那么子類的實現也不得不發生改變;
由于組合或聚合關系可以將已有的對象(也可稱為成員對象)納入到新對象中,使之成為新對象的一部分,因此
新對象可以調用已有對象的功能,這樣做可以使得成員對象的內部實現 細節對于新對象不可見。
實例:圖書管理系統中,如果數據在MySQL中,我們需要創建一個鏈接MySQL的工具類 MySQLUtil ,Dao只需要繼承該工具類即可操作數據庫,如果把數據庫換成Oracle,我們需要新建一個工具類 OracleUtil ,Dao需要修改繼承對象改為 OracleUtil ,這就違反了開閉原則。
原始設計方案
基于合成復用原則進行重構:
我們把 OracleUtil 作為 MySQLUtil 的子類,BookDao中把 MySQLUtil 作為一個屬性組合進來,每次需要變更數據庫鏈接的時候,只需要修改BookDao的依賴注入配置文件即可。這里符合里氏替換原則。
1.2.7 迪米特法則
定義:一個軟件實體應當盡可能少地與其他實體發生相互作用。
講解:如果一個系統符合迪米特法則,那么當其中某一個模塊發生修改時,就會盡量少地影響其他 模塊,擴展會相對容易,這是對軟件實體之間通信的限制,迪米特法則要求限制軟件實體之 間通信的寬度和深度。迪米特法則可降低系統的耦合度,使類與類之間保持松散的耦合關系。
迪米特法則要求我們在設計系統時,應該盡量減少對象之間的交互,如果兩個對象之間不必 彼此直接通信,那么這兩個對象就不應當發生任何直接的相互作用,如果其中的一個對象需 要調用另一個對象的某一個方法的話,可以通過第三者轉發這個調用。簡言之,就是通過引 入一個合理的第三者來降低現有對象之間的耦合度。
作用:降低系統的耦合度
實例:我們在做增刪改查的時候,如果直接用控制層調用Dao,業務處理的關系會比較亂,我們需要合理增加一個中間對象(業務層)來解決個問題。
原始設計方案
基于迪米特法則進行重構
1.3 設計模式分類
GOF中共提到了23種設計模式不是孤立存在的,很多模式之間存在一定的關聯關系,在大的系統開發中常常同時使用多種設計模式。這23種設計模式根據功能作用來劃分,可以劃分為3類:
(1)創建型模式:用于描述“怎樣創建對象”,它的主要特點是“將對象的創建與使用分離”,單例、原型、工廠方法、抽象工廠、建造者5種設計模式屬于創建型模式。
(2)結構型模式:用于描述如何將類或對象按某種布局組成更大的結構,代理、適配器、橋接、裝飾、外觀、享元、組合7種設計模式屬于結構型模式。
(3)行為型模式:用于描述類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,以及怎樣分配職責。模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器11種設計模式屬于行為型模式。
GOF的23種設計模式
1、單例(Singleton)模式:某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多
例模式。
2、原型(Prototype)模式:將一個對象作為原型,通過對其進行復制而克隆出多個和原型類似的新實例。
3、工廠方法(Factory Method)模式:定義一個用于創建產品的接口,由子類決定生產什么產品。
4、抽象工廠(AbstractFactory)模式:提供一個創建產品族的接口,其每個子類可以生產一系列相關的產品。
5、建造者(Builder)模式:將一個復雜對象分解成多個相對簡單的部分,然后根據不同需要分別創建它們,最后構建成該
復雜對象。
6、代理(Proxy)模式:為某對象提供一種代理以控制對該對象的訪問。即客戶端通過代理間接地訪問該對象,從而限制、
增強或修改該對象的一些特性。
7、適配器(Adapter)模式:將一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的
那些類能一起工作。
8、橋接(Bridge)模式:將抽象與實現分離,使它們可以獨立變化。它是用組合關系代替繼承關系來實現,從而降低了抽
象和實現這兩個可變維度的耦合度。
9、裝飾(Decorator)模式:動態的給對象增加一些職責,即增加其額外的功能。
10、外觀(Facade)模式:為多個復雜的子系統提供一個一致的接口,使這些子系統更加容易被訪問。
11、享元(Flyweight)模式:運用共享技術來有效地支持大量細粒度對象的復用。
12、組合(Composite)模式:將對象組合成樹狀層次結構,使用戶對單個對象和組合對象具有一致的訪問性。
13、模板方法(TemplateMethod)模式:定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以
不改變該算法結構的情況下重定義該算法的某些特定步驟。
14、策略(Strategy)模式:定義了一系列算法,并將每個算法封裝起來,使它們可以相互替換,且算法的改變不會影響
使用算法的客戶。
15、命令(Command)模式:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。
16、職責鏈(Chain of Responsibility)模式:把請求從鏈中的一個對象傳到下一個對象,直到請求被響應為止。通
過這種方式去除對象之間的耦合。
17、狀態(State)模式:允許一個對象在其內部狀態發生改變時改變其行為能力。
18、觀察者(Observer)模式:多個對象間存在一對多關系,當一個對象發生改變時,把這種改變通知給其他多個對象,
從而影響其他對象的行為。
19、中介者(Mediator)模式:定義一個中介對象來簡化原有對象之間的交互關系,降低系統中對象間的耦合度,使原有
對象之間不必相互了解。
20、迭代器(Iterator)模式:提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
21、訪問者(Visitor)模式:在不改變集合元素的前提下,為一個集合中的每個元素提供多種訪問方式,即每個元素有多
個訪問者對象訪問。
22、備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取并保存一個對象的內部狀態,以便以后恢復它。
23、解釋器(Interpreter)模式:提供如何定義語言的放法,以及對語言句子的解釋方法,即解釋器。
2 設計模式常用案例
2.1 單例模式
單例模式(Singleton Pattern)是 JAVA 中最常見的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
單例模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。該類還提供了一種訪問它唯一對象的方式,其他類可以直接訪問該方法獲取該對象實例,而不需要實例化該類的對象。
單例模式特點:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
單例模式優點:
1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例。
2、避免對資源的多重占用(比如寫文件操作)
單例模式真實應用場景
1、網站的計數器
2、應用程序的日志應用
3、數據庫連接池設計
4、多線程的線程池設計
2.1.1 單例模式-餓漢式
創建一個單例對象 SingleModel , SingleModel 類有它的私有構造函數和本身的一個靜態實例。SingleModel 類提供了一個靜態方法,供外界獲取它的靜態實例。 DesignTest 我們的演示類使用 SingleModel 類來獲取 SingleModel 對象。
2.1.2 多種單例模式講解
單例模式有多種創建方式,剛才創建方式沒有特別的問題,但是程序啟動就需要創建對象,不管你用不用到對象,都會創建對象,都會消耗一定內存。因此在單例的創建上出現了多種方式。
懶漢式:
懶漢式有這些特點:
1、延遲加載創建,也就是用到對象的時候,才會創建
2、線程安全問題需要手動處理(不添加同步方法,線程不安全,添加了同步方法,效率低)
3、實現容易
案例如下: SingleModel1
如果在創建對象實例的方法上添加同步 synchronized ,但是這種方案效率低,代碼如下:
雙重校驗鎖: SingleModel2
這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。
3 Spring設計模式剖析
Spring是一個分層的JavaSE/EE full-stack(一站式) 輕量級開源框架,非常受企業歡迎,他解決了業務邏輯層和其他各層的松耦合問題,它將面向接口的編程思想貫穿整個系統應用。在Spring源碼中擁有多個優秀的設計模式使用場景,有非常高的學習價值。
3.1 觀察者模式
定義: 對象之間存在一對多或者一對一依賴,當一個對象改變狀態,依賴它的對象會收到通知并自動更新。
MQ其實就屬于一種觀察者模式,發布者發布信息,訂閱者獲取信息,訂閱了就能收到信息,沒訂閱就收不到信息。
優點:
1、觀察者和被觀察者是抽象耦合的。
2、建立一套觸發機制
缺點:
1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
3.1.1 Spring觀察者模式
ApplicationContext 事件機制是觀察者設計模式的實現,通過 ApplicationEvent 類和 ApplicationListener 接口,可以實現 ApplicationContext 事件處理。
如果容器中有一個 ApplicationListener Bean ,每當 ApplicationContext 發布 ApplicationEvent 時,ApplicationListener Bean 將自動被觸發。這種事件機制都必須需要程序顯示的觸發。
其中spring有一些內置的事件,當完成某種操作時會發出某些事件動作。比如監聽 ContextRefreshedEvent 事件,當所有的bean都初始化完成并被成功裝載后會觸發該事件,實現ApplicationListener<ContextRefreshedEvent> 接口可以收到監聽動作,然后可以寫自己的邏輯。同樣事件可以自定義、監聽也可以自定義,完全根據自己的業務邏輯來處理。
對象說明:
1、ApplicationContext容器對象
2、ApplicationEvent事件對象(ContextRefreshedEvent容器刷新事件)
3、ApplicationListener事件監聽對象
3.1.2 ApplicationContext事件監聽
當ApplicationContext內的Bean對象初始化完成時,此時可以通過監聽 ContextRefreshedEvent 得到通知!我們來
模擬一次。
創建監聽對象:
ApplicationContextListener
將對象添加到容器中:
測試
此時會打印如下信息:
應用場景:
程序啟動,初始化過程中,需要確保所有對象全部初始化完成,此時在從容器中獲取指定對象做相關初始化操作。例如:將省、市、區信息初始化到緩存中。
3.1.3 自定義監聽事件
自定義監聽事件可以監聽容器變化,同時也能精確定位指定事件對象,我們編寫一個案例演示自定義監聽事件實現流
程。
定義事件監聽對象: MessageNotifier
定義事件對象: MessageEvent
將對象添加到容器中:
添加事件測試
測試打印結果如下:
3.2 代理模式
定義:給某對象提供一個代理對象,通過代理對象可以訪問該對象的功能。主要解決通過代理去訪問[不能直接訪問的對象,例如租房中介,你可以直接通過中介去了解房東的房源信息,此時中介就可以稱為代理。
優點:
1、職責清晰。
2、高擴展性。
3、智能化。
缺點
1、由于在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
2、實現代理模式需要額外的工作,有些代理模式的實現非常復雜。
代理實現方式:
基于接口的動態代理
提供者:JDK官方的Proxy類。
要求:被代理類最少實現一個接口。
基于子類的動態代理
提供者:第三方的CGLib,如果報asmxxxx異常,需要導入asm.jar。
要求:被代理類不能用final修飾的類(最終類)。
3.2.1 JDK動態代理
JDK動態代理要點:
1、被代理的類必須實現一個接口
2、創建代理對象的時候,用JDK代理需要實現InvocationHandler
3、代理過程在invoke中實現
我們以王五租房為例,王五通過中介直接租用戶主房屋,中介在這里充當代理角色,戶主充當被代理角色。
創建房東接口對象: LandlordService
創建房東對象: Landlord
創建代理處理過程對象: QFangProxy
創建代理,并通過代理調用房東方法: JdkProxyTest
運行結果如下:
3.2.2 CGLib動態代理
CGLib動態代理要點:
1、代理過程可以實現MethodInterceptor(Callback)接口中的invoke來實現
2、通過Enhancer來創建代理對象
在上面的案例基礎上,把 QFangProxy 換成 SFangProxy ,代碼如下:
創建測試類: CGLibProxyTest ,代碼如下
3.2.3 Spring AOP-動態代理
基于SpringAOP可以實現非常強大的功能,例如聲明式事務、基于AOP的日志管理、基于AOP的權限管理等功能,利用AOP可以將重復的代碼抽取,重復利用,節省開發時間,提升開發效率。Spring的AOP其實底層就是基于動態代理而來,并且支持JDK動態代理和CGLib動態代理,動態代理的集中體現在 DefaultAopProxyFactory 類中,我們來解析下 DefaultAopProxyFactory 類。
如果我們在spring的配置文件中不配置 <aop:config proxy-target-class="true"> ,此時默認使用的將是JDK動態代理,如果配置了,則會使用CGLib動態代理。
JDK動態代理的創建 JdkDynamicAopProxy 如下:
CGLib動態代理的創建 ObjenesisCglibAopProxy 如下:
3.3 工廠設計模式
定義:工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。它負責實現創建所有實例的內部邏輯。工廠類的創建產品類的方法可以被外界直接調用,創建所需的產品對象。
優點:
1、一個調用者想創建一個對象,只要知道其名稱就可以了。
2、屏蔽產品的具體實現,調用者只關心產品的接口。
3、降低了耦合度
3.3.1 工廠模式案例
我們來做這么一個案例,創建一個接口Product和Product的實現類Mobile以及Car,再定義一個具體的工廠對象ProcutFactory,并通過ProductFactory來獲取指定的Product。
Product接口:
創建接口實現類Mobile:
創建接口實現類Car:
創建工廠ProductFactory,根據參數創建指定產品對象:
使用工廠ProductFactory創建指定對象:
運行結果如下
3.3.2 BeanFactory工廠模式
Spring內部源碼也有工廠模式的實現,并且解決了上面我們提到的工廠模式的缺陷問題。
Spring中的BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得Bean對象,但是否是在傳入參數后創建還是傳入參數前創建這個要根據具體情況來定
BeanFactory源碼:
在BeanFactory接口中,有多個getBean方法,該方法其實就是典型的工廠設計模式特征,在接口中定義了創建對象的方法,而對象如何創建其實在接口的實現類中實現
DefaultListableBeanFactory 。
我們用Spring的工廠對象BeanFactory來解決上面工廠模式案例所帶來的問題
案例:
測試:
結果:
使用Spring的BeanFactory,以后要新增一個產品,只需要創建產品對應的xml配置即可,而不需要像ProductFactory 那樣硬編碼存在。
3.4 適配器模式
定義
將一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不兼容而不能一起工作的那些類能一起工作
優點:
1、可以讓任何兩個沒有關聯的類一起運行。
2、提高了類的復用。
3、靈活性好
缺點:
過多地使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是 A 接口,其實內部被適配成了 B 接口的實現,一個系統如果太多出現這種情況,無異于一場災難。
3.4.1 Spring Aop適配器+代理模式案例
Spring架構中涉及了很多設計模式,本文來介紹下Spring中在AOP實現時Adapter模式的使用。AOP本質上是Java動態代理模式的實現和適配器模式的使用。
我們基于Spring的前置通知來實現一個打卡案例,再基于前置通知講解前置適配模式。
創建打卡接口: PunchCard 定義打卡方法,代碼如下:
定義打卡實現: PunchCardImpl 實現打卡操作,代碼如下:
前置通知創建: PunchCardBefore 實現在打卡之前識別用戶身份,代碼如下:
spring.xml配置前置通知:
測試效果如下:
3.4.2 Spring AOP適配器體系
前置通知其實就是適配器模式之一,剛才我們編寫的前置通知實現了接口 MethodBeforeAdvice 。Spring容器將每個具體的advice封裝成對應的攔截器,返回給容器,這里對advice轉換就需要用到適配器模式。我們來分析下適配器的實現:
如下代碼實現了接口 BeforeAdvice ,而 BeforeAdvice 繼承了 Advice 接口,在適配器接口 AdvisorAdapter 里面定義了方法攔截。
AdvisorAdapter :定義了2個方法,分別是判斷通知類型是否匹配,如果匹配就會獲取對應的方法攔截。
MethodBeforeAdviceAdapter :實現了 AdvisorAdapter ,代碼如下:
剛才我們在spring.xml中配置了代理類,代理類通過
DefaultAdvisorAdapterRegistry類來注冊相應的適配器,我們可以在
4 架構中的設計模式
我們結合上面所學的設計模式,開發一款框架,該框架具備Spring的功能和SpringMVC功能,這里我們會提供部分工具,直接供大家使用。開發的框架流程圖如上,整體基于Servlet實現。
準備工作:
搭建一個工程,引入相關依賴包,以及工具包,pom.xml代碼如下:
擴展
在 AccountController 類上有幾個@RequestMapping注解,這個注解是我們自定義的,這個注解上有對應的值,我們可以通過 ParseAnnotation 工具類解析該注解,并將解析值存儲到Map中,修改 BaseInit ,代碼如下:
解析這個注解的作用,可以通過用于請求的路徑來判斷該路徑歸哪個對象處理;
此時不要忘了配置web.xml
4.1 自定義框架-適配器視圖渲染
4.1.1 流程分析
在我們做視圖解析的時候,有不同的解析方式,比如有直接輸入json數據、重定向、轉發、輸出文件流等多種方式。通常采用哪種解析方式由執行方法的返回值決定,例如返回一個字符串,我們可以把返回的字符串當做響應的頁面,這時候可以采用轉發的方式,如果返回的是一個javabean,這時候我們可以采用輸出json字符串的方式解析。像這一塊的實現,我們可以采用適配器模式實現。
實現步驟如下:
1、定義一個視圖解析接口ViewHandler,提供2種解析方式,分別為json輸出和forward
2、為接口實現每種解析方式。分別創建PrintViewHandler和ForwardViewHandler
3、創建一個視圖渲染接口View,View中提供渲染方法render
4、創建View的渲染實現ViewAdapter,通過提供的相應結果,來創建對應的試圖解析器,并調用解析方式
4.1.2 適配器模式實現視圖解析
1)創建視圖解析器接口
2)創建json解析和轉發
JSON解析對象: PrintViewHandler
轉發解析對象: ForwardViewHandler
3)視圖渲染接口和實現
視圖渲染接口: View
視圖渲染實現: ViewAdapter
我們創建一個類 DispacherServlet ,繼承BaseInit,同時在 web.xml 中把 BaseInit 換成 DispacherServlet ,并重寫 service 方法,實現攔截所有用戶請求,在 service 中使用執行反射調用,然后調用剛才寫好的適配器查找對應的渲染方式執行渲染,代碼如下:
我們來測試一下,請求 <
http://localhost:18081/account/info>
請求 <
http://localhost:18081/account/one>
4.2 自定義框架-觀察者模式
上面雖然已經實現了MVC模型對應功能,但是每次調用對象都是創建了新對象,我們可以對這里進行優化,讓每次調用的對象是單例對象,這時候我們就需要初始化的時候把對象創建好了,但是對象和對象之間又存在依賴關系,我們可以在配置文件中配置這種關系。這里我們可以使用觀察者模式和單例模式。
4.2.1 流程分析
我們編寫一個類似Spring的MVC框架,這里采用觀察者模式實現監聽文件加載,實現步驟如下:
1、編寫BaseInit類,并繼承HttpServlet
2、重寫HttpServlet中的init(ServletConfig config)方法
3、編寫一個抽象類ParseFile,在該類中編寫監聽的對象baseInit,同時編寫一個通知方法load(InputStream is)
4、編寫ParseXml繼承ParseFile,實現load(InputStream is)方法,當BaseInit中的init方法加載到變更文件
時,該方法將得到變更通知。
4.2.2 監聽文件加載并解析文件
要創建實例的對象信息以及依賴關系信息,我們可以配置到配置文件中,配置如下:
講解
1、配置文件中一個bean表示要創建一個對象的實例
2、id表示創建對象后的唯一id
3、class表示要創建哪個對象的全限定名
4、property表示給創建的對象指定屬性賦值
5、property.name表示給指定對象的指定屬性賦值
6、property.ref表示將指定對象賦值給property.name指定的屬性
我們需要加載哪個配置文件進行解析,需要在web.xml中配置,代碼如下:
當 BaseInit 獲取到要解析的文件內容時,通知 ParseFile 解析對應的配置文件,在這里我們可以使用觀察者模式,讓當前 BaseInit 和 ParseFile 關聯,這里 ParseFile 是一個抽象對象,給它編寫一個實現 ParseXml ,以后也許還會寫 ParseYaml 等。這里用到的設計模式是觀察者模式,觀察到要解析配置文件,通知指定對象進行解析。
創建 ParseFile ,代碼如下
創建 ParseXml ,代碼如下:
修改 BaseInit ,代碼如下
4.3 自定義框架-工廠模式
此時我們通知 ParseXml 加載需要解析的配置文件,這時候我們需要創建一個工廠對象來實現對象創建和對象獲取,這塊我們可以采用工廠模式實現
4.3.1 流程分析
工廠的流程如上圖,工廠需要先解析 spring.xml ,將解析的實體bean存儲起來,用戶每次獲取對應的實例時,可以通過請求uri獲取,也可以通過 spring.xml 中唯一的id獲取
4.3.2 工廠模式實現獲取對象實例
1)創建工廠接口
創建工廠接口 BeanFactory ,因為以后可能會有注解實現方式,所以這里為工廠創建一個接口,代碼如下:
2)工廠實現
創建工廠實現類 XmlBeanFactory ,同時實現根據url和根據id獲取對應實例方法,代碼如下:
工廠測試:
3)重寫service反射調用
為了每次能使用單例對象處理用戶請求從而更節省資源,我們需要根據用戶的uri找到對應的實例。
我們可以按照這個步驟實現通過uri找到對應的實例:
1、循環所有methods,獲取每個method對應的uri和method對應的Class
2、匹配beans中實例的Class和method的Class相同的對象,獲取對應的key(也就是id)
3、把uri和id重新組合成一個新的Map,叫urlIdMaps
4、用戶每次請求的時候,直接通過uri從urlIdMaps中獲取對應實例
這個操作,該
ParseAnnotation.parseUrlMappingInstance(methods,beans) 方法已經幫我們實現了,所以我們只需要把實現的整體流程搬到 XmlBeanFactory 中就可以了。
修改 XmlBeanFactory ,代碼如下:
4.4 自定義框架-代理模式增強
4.4.1 流程分析
我們模擬Spring的聲明式事務控制,在業務層進行增強,我們可以按照如下步驟實現:
1、創建增強類TransactionManager,在里面編寫一個增強方法begin
2、創建一個BeanProxy對象,用于給指定包下的對象創建代理
3、每次ParseXml加載解析之后,調用BeanProxy給指定包下的對象創建代理
4.4.2 業務層代理模式增強
1)指定增強位置
我們首先在spring.xml配置文件中配置一下增強的位置,before表示前置增強, package 表示指定包下的對象進行前
置增強, ref 表示指定增強的類, method 表示增強的類中指定的方法。
我們需要創建一個增強類 TransactionManager :
2)增強實現
我們接著需要對增強進行實現,我們可以按照如下步驟實現:
1、解析xml,需要獲取增強的包,實現增強的類的方法
2、獲取所有解析的實例,并判斷每個實例是否是該包下的對象
3、如果是該包下的對象,給它創建代理,執行增強
4、將當前對象替換成代理對象
5、將當前對象下的引用屬性替換成代理
我們創建該代理增強類 BeforeProxyBean ,代碼如下:
創建代理類 ProxyBeanFactory ,代碼如下:
在工廠 XmlBeanFactory 實現調用即可:
運行測試結果如下:
若有收獲,就點個贊吧