设计模式实践及总结
一、設計模式概念及設計原則
? ? ? ?記得剛開始學編程的時候就看過設計模式相關的書籍,雖然當時有很多地方都不理解,但是建立了早期對架構設計的意識,讓后面的學習和工作中受益匪淺,最近兩年也一直在做架構設計方面的工作,解開了之前很多的困惑,也形成了一些自己的思想,我需要把自己零散的想法系統的整理出來,如果能對大家有幫助當然更好了。為什么選擇設計模式?因為架構設計是一個很寬泛甚至對于有些人是一個很空虛的領域,設計模式的好處就在早就已經有些很成熟被大家認可的模式。既然已經有了很成熟的模式我為什么還要重復造輪子呢?目前網上很多關于設計模式的文章都是講解各個模式的規則、目的,很少有直接應用到實際工程的例子,所以我在設計模式后面加了個最佳實踐,目的就是通過工程實踐把自己在架構設計方面的積累轉換成大家容易理解和熟悉的設計模式來表達出來。
1.1、面向對象六大設計原則
(1)、單一職責原則(Single Responsibility Principle,簡稱SRP)
? ? ? ?就一個類而言,應該僅有一個引起它變化的原因。通俗的說,即一個類只負責一項職責。這是一個備受爭議卻又及其重要的原則,因為單一職責的劃分界限并不是總是那么清晰,很多時候都是需要靠個人經驗來界定,如何劃分一個類、一個函數的職責,每個人都有自己的看法,這需要根據個人經驗、具體的業務邏輯而定。但是它也有一些基本的指導原則,例如兩個完全不一樣的功能就不應該放在一個類中。
(2)、開閉原則(Open Close Principle,簡稱OCP)
? ? ? ?對擴展開放,對修改關閉。開閉原則是讓程序更穩定、更靈活的一個基本保證。程序中的對象(類、模塊、函數等)應該對于擴展是開放的,但是,對于修改是封閉的,在軟件的生命周期內,因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會將錯誤引入原本已經經過測試的舊代碼中,破壞原有系統。因此,當軟件需要變化時,我們應該盡量通過擴展的方式來實現變化,而不是通過修改已有的代碼。
(3)、接口隔離原則(InterfaceSegregation Principles,簡稱ISP)
? ? ? ?客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。為依賴接口的類定制服務,只暴露給調用的類它需要的方法,它不需要的方法則隱藏起來,只有專注地為一個模塊提供定制服務,才能建立最小的依賴關系,提高內聚,減少對外交互,使接口用最少的方法去完成最多的事情。接口盡量小,但是要有限度。對接口進行細化可以提高程序設計靈活性是不掙的事實,但是如果過小,則會造成接口數量過多,使設計復雜化,所以一定要適度。
(4)、里氏替換原則(Liskov Substitution Principle,簡稱LSP)
? ? ? ?所有引用基類的地方必須能透明地使用其子類的對象。面向對象的語言的三大特點是繼承、封裝、多態,里氏替換原則就是依賴于繼承、多態這兩大特性。里氏替換原則簡單來說就是,所有引用基類的地方必須能透明地使用其子類的對象。通俗點講,只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是反過來就不行了,有子類出現的地方,父類未必就能適應。說了那么多,其實最終總結就兩個字:抽象。
(5)、依賴倒置原則(Dependence Inversion Principle,簡稱DIP)
? ? ? ?依賴反轉原則指代了一種特定的解耦形式,使得高層次的模塊不依賴于低層次的模塊的實現細節的目的,依賴模塊被顛倒了。這種表達真的讓人很難理解,簡單點:依賴倒置原則在 Java 語言中的表現就是:模塊間通過接口依賴,實現類之間不發生直接的依賴關系。再簡單點:面向接口編程,或者說是面向抽象編程
(6)、迪米特原則(Law of Demeter,簡稱LOD)
? ? ? ?也稱為最少知識原則(Least Knowledge Principle):一個對象應該對其他對象有最少的了解,也就是關于如何松耦合,一個類應該對自己需要耦合或調用的類知道得最少,類的內部如何實現、如何復雜都與調用者或者依賴者沒關系,調用者或者依賴者只需要知道他需要的方法即可,其他的我一概不關心。類與類之間的關系越密切,耦合度越大,當一個類發生改變時,對另一個類的影響也越大。
二、設計模式實踐及總結
2.1、簡單工廠模式
? ? ? ?簡單工廠模式 又稱為 靜態工廠模式
? ? ? ?模式場景:在一個披薩店中,要根據不同客戶的口味,生產不同的披薩,如素食披薩、希臘披薩等披薩。
? ? ? ?凡是看到一個工廠Factory,然后一個靜態方法,靜態方法一個參數,那么很可能就是用到了 簡單工廠 模式。
2.2、工廠方法模式
? ? ? ?client不需要知道具體產品,只需要知道具體工廠 即可。
? ? ? ?在披薩實例中,如果我想根據地域的不同生產出不同口味的披薩,如紐約口味披薩,芝加哥口味披薩。如果利用簡單工廠模式,我們需要兩個不同的工廠,NYPizzaFactory、ChicagoPizzaFactory。在該地域中有很多的披薩店,他們并不想依照總店的制作流程來生成披薩,而是希望采用他們自己的制作流程。這個時候如果還使用簡單工廠模式,因為簡單工廠模式是將披薩的制作流程完全承包了。那么怎么辦?
? ? ? ?這樣解決:將披薩的制作方法交給各個披薩店完成,但是他們只能提供制作完成的披薩,披薩的訂單處理仍然要交給披薩工廠去做。也就是說,我們將createPizza()方法放回到PizzaStore中,其他的部分還是保持不變。
? ? ? ?示例有:日志記錄器
? ? ? ?凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以只能生產一個產品, 很可能就是用到了 工廠方法 模式。?
2.3、抽象工廠模式
? ? ? ?依然是披薩店。為了要保證每家加盟店都能夠生產高質量的披薩,防止使用劣質的原料,我們打算建造一家生產原料的工廠,并將原料運送到各家加盟店。但是加盟店都位于不同的區域,比如紐約、芝加哥。紐約使用一組原料,芝加哥使用另一種原料。在這里我們可以這樣理解,這些不同的區域組成了原料家族,每個區域實現了一個完整的原料家族。
? ? ? ?各個區域的加盟店的同一款產品是一個維度,是產品 等級結構
? ? ? ?某個區域的加盟店的所有款產品是一個維度,是產品 族。
? ? ? ?抽象工廠類是不會涉及 加盟店的,抽象產品類的所有產品構成 產品族。
? ? ? ?加盟店是具體的工廠類,是對抽象工廠類的實現。
? ? ? ?凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以生產多個產品, 很可能就是用到了 抽象工廠 模式。?
2.4、建造者模式
? ? ? ?建造者模式構建復雜對象就像造汽車一樣,是一個一個組件一個一個步驟創建出來的,它允許用戶通過制定的對象類型和內容來創建他們,但是用戶并不需要知道這個復雜對象是如何構建的,它只需要明白通過這樣做我可以得到一個完整的復雜對象實例。
KFC里面一般都有好幾種可供客戶選擇的套餐,它可以根據客戶所點的套餐,然后在后面做這些套餐,返回給客戶的事一個完整的、美好的套餐。(構造過程抽象統一為buildFood,buildDrink)
? ? ? ?示例有:游戲角色設計
? ? ? ?凡是看到出現以Director、Builder結尾、或包含build、 construct方法 的類, 很可能就是用到了 建造者模式。
2.5、原型模式
? ? ? ?Ctrl+C、Ctrl+V、克隆、復制簡歷、復制Xxx。。。
? ? ? ?示例有:大同小異的工作周報、帶附件的周報
? ? ? ?凡是看到出現以Prototype結尾、或包含clone、copy方法 的類, 很可能就是用到了 原型模式。
2.6、單例模式
? ? ? ?如果制造出多個實例,會導致很多問題產生的情況。
? ? ? ?通常是重量級的類,比如tomcat的ServletContext,Hibernate的SessionFactory,Spring的BeanFactory,通常只有一個。
? ? ? ?單例模式一般通過靜態的實例變量來實現,可以想象的是,如果這個全局唯一的 靜態的實例變量 能夠保證其創建的 其他Subject(比如Bean)也是只有一個, 那么那些Subject 也是單例的, 盡量“那些Subject” 本身并本身 單例模式~ 比如, 我們Spring 容器中的單例的Bean。 單例的Bean 并不是設計模式中單例模式, ?只是其scope是 Spring 容器唯一。這個是兩碼事,大家應該還? ? ? ?是能夠區分清楚的吧!
? ? ? ?Spring 容器何保證,其單例的Bean,每次獲取得到的 都是同一個? 一般,我們的通過緩存, 即緩存到內存。
? ? ? ?通過“ ? Spring 容器中的單例的BeanSpring 容器中的單例的Bean ? ”這樣的方式, 我們實際上可以實現一個 單例鏈或 單例實例鏈: 最開始的某些對象是靜態單例的,其創建的bean 不是單例,不是靜態,卻是唯一的,bean 可以?創建其他 “ ?bean 不是單例,不是靜態,卻是唯一的 ”, 過程反復, 就形成了一個鏈條。
? ? ? ?當然我們說全局, 這個局有多大,仍然是個相對的概念。
? ? ? ?示例有:太多
? ? ? ?凡是看到 Singleton getSingleton, 很可能就是用到了 單例模式。 ?雖然實踐中 不一定使用 Singleton 這個詞, 但這個是常見的。是良好的實際。?
2.7、適配器模式
? ? ? ?我們工作電壓11V的電腦,需要在 220V的 輸電電壓下 工作, 我們就需要 電源適配器; 機器人想要模擬人的操作,那么也需要進行 某些適配工作。
? ? ? ?示例有:沒有源碼的算法庫
? ? ? ?Target 雖然常見,但是我們一般不會使用這個詞,而是直接使用我們實際的類的名稱。凡是看到出現以Adapter、Adaptee結尾、或包含request,specialRequest 方法 的類, 很可能就是用到了 適配器 模式。盡管如此,我們實際中可能不會使用 request,specialRequest 這樣的詞, 而是直接、具體的 接口名字。
2.8、橋接模式
? ? ? ?假設圖形類有兩個關鍵屬性: 形狀、顏色。我們有 正方形、長方形、圓形,有三種顏色:白色、灰色、黑色,那么我們可以畫出3*3=9中圖形。 那么 我們是不是就需要九個 class類呢? 不需要的,通過橋接模式,我們只需要 3+3 = 6 個類。
可以想象,如果形狀、顏色 更多,那么 橋接模式 就非常有效的減少了類的數量。
? ? ? ?橋接模式 好像和 裝飾模式有異曲同工之妙,它們都可以有效的減少了類的數量, 防止類數量爆炸。 但是這里的例子中,裝飾模式是不合適的, 因為 形狀、顏色 是兩個獨立的屬性,沒有任何相關關系的。
? ? ? ?橋接模式 是對 不同屬性, 從不同維度的 拆分。屬性之間 僅僅是一個 聚合關系。
? ? ? ?裝飾模式 是對 不同行為,圍繞某個主要行為 進行 裝飾。需要有繼承or實現關系。
?? ? ? ?示例有:跨平臺圖像瀏覽系統
? ? ? ?凡是看到出現以RefinedAbstraction、Implementor結尾 的類, 很可能就是用到了 橋接模式。 雖然實際中 很可能不會使用 RefinedAbstraction、Implementor 這樣的詞來命名我們的類名,但是, 仍然可以做一個判斷。
2.9、組合模式
? ? ? ?凡是涉及文件系統的瀏覽遍歷、操作等的, 比如 殺毒軟件對 文件系統進行殺毒。
? ? ? ?語法樹,比如XML,HTML,JSON 常常存在 “容器” 元素, 就很適合 組合模式 ?。比如 HTML 語法樹,我們可以把 div 當做一個 容器,div 可以嵌套各種子元素。
? ? ? ?示例有:Configuration、Bean、Component
? ? ? ?凡是看到出現以Composite、Leaf、combination、assembly、group結尾、或包含filter、 handle、intercept 方法 以及 add、remove、getChild 方法 的類, 很可能就是用到了 裝飾器模式。?
2.10、裝飾器模式
? ? ? ?需要動態地給一個對象增加功能,這些功能也可以動態地被撤銷。 ?當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴展和維護時。
? ? ? ?裝飾者模式的關鍵詞, 一定是“動態、靈活”, 通過繼承,我們可以做很多事情,但是顯得十分臃腫,十分不動態,類數量(m*n)爆炸。裝飾者模式可以避免這些, 將類數量 變化 m + n
? ? ? ?其實,裝飾模式和適配器模式都是“包裝模式(Wrapper Pattern)”。 另外代理模式,好像也有包裝 功能,但是要求、側重點是不同的。
? ? ? ?示例有:圖形界面構件庫
? ? ? ?凡是看到出現以Decorator、Filter、Interceptor 、Wrapper 結尾、或包含filter、 handle、intercept 方法 的類, 很可能就是用到了 裝飾器模式。
2.11、外觀/門面模式
? ? ? ?非常籠統而寬泛的模式
?? ? ? ?示例有:B/S系統的首頁等等
? ? ? ?凡是看到出現以Facade、System、Manager 結尾 的類, 很可能就是用到了 外觀/門面模式。 沒錯,這里有個Manager ,Manager 是一個范疇很大的詞。
2.12、享元模式
? ? ? ?其實就是 對一系列類似的 對象, 實現了 單例模式, 然后統一管理。
? ? ? ?示例有:ThreadLocal,Redis緩存。http、redis、mq、kafka、String常量池、數據庫連接池、緩沖池
? ? ? ?凡是看到出現以Flyweight 結尾 的類, 很可能就是用到了享元模式。實踐中,我們可能不會使用Flyweight這樣的詞,而是具體的 “ 元 ” 的類名, 而判斷是否是 享元, 關鍵是 判斷是否有一個 map 存在, 其 get 方法是否會判斷“元” 是否已經存在,是否包含了 put 操作。?
2.13、代理模式
? ? ? ?web 代理服務器、Nginx反向代理,JDK動態代理,CGlib 動態代理
? ? ? ?1、 遠程代理:為一個對象在不同的地址空間提供局部代表。這樣可以隱藏一個對象存在于不同地址空間的事實。
? ? ? ?2、 虛擬代理:通過使用過一個小的對象代理一個大對象。這樣就可以減少系統的開銷。
? ? ? ?3、 保護代理:用來控制對真實對象的訪問權限。
? ? ? ?示例有:收費商務信息查詢系統
? ? ? ?凡是以Proxy 結尾,或看到出現包含preRequest,postRequest 等 preXxx、postXxx 方法 的類,或看到 InvocationHandler, 很可能就是用到了代理模式。
2.14、職責鏈模式
? ? ? ?凡是那些涉及 不同等級層次的 操作、流程,不同等級有不同權限、功能,或需要 層層審批。那么就適合。
? ? ? ?示例有:采購單的分級審批、請假流程、報銷審批流程。。。
? ? ? ?凡是看到出現以Chain、pipeline 結尾、或包含handler,handleRequest 方法 的類, 很可能就是用到了責任鏈模式。
2.15、命令模式
? ? ? ?Invoker (調用者)本來可以直接調用Receiver(接收者), 但是Invoker 覺得麻煩,不想、不需或不能 管太多 ,那么我們引入一個 Command 中間層,那么 就實現了解耦。
? ? ? ?一些場景:控制面板、遙控器
? ? ? ?示例有:自定義功能鍵、撤銷操作、宏命令
? ? ? ?凡是看到出現以Command、Order 、Invoker、Receiver 結尾、或包含execute、call、action 方法 的類, 很可能就是用到了 命令模式。
2.16、解釋器模式
? ? ? ?語法分析解釋權、抽象語法樹,翻譯機。對不懂的外國語進行 翻譯。等等
? ? ? ?示例有:機器人控制程序
? ? ? ?凡是看到出現以Expression/ translate/ interpret 結尾、或包含 translate/ interpret 方法 的類, 很可能就是用到了 解釋器模式。
2.17、迭代器模式
? ? ? ?Aggregate和 Iterator 分離。為什么需要分離? Iterator 不需要知道Aggregate的數據是怎么來的, 可以按照需要的 方式進行遍歷。 比如對電視機的各個有限電視臺的遍歷,我們可以 按照 各個省、地方電視臺的順序,一個省的所有電視臺遍歷完了之后,再其他省; 也可以按照 先遍歷所有省的 娛樂頻道,然后遍歷所有省的 經濟頻道,然后。。
HaspMap的 迭代器。
?? ? ? 示例有:JDK內置迭代器、銷售管理系統中數據的遍歷
? ? ? ?凡是看到出現以Iterator、Loop 結尾、或包含hasNext,next,hasMore方法 的類, 很可能就是用到了 迭代器模式。
2.18、中介者模式
? ? ? ?中介,就是我們平常所理解的 中介。適合某些 雙方/多方 不宜 直接對話的情況。 中介者, 當然,需要一定的 權威性、權力、公信度。
? ? ? ?同事之間 雖然可以直接1對1,溝通,但某些時刻需要通過部門負責人來 中介、協調。同學之間 需要通過 輔導員、班主任來溝通、 統一領導、指揮。男方和 女方 通過婚介所 進行聯誼。 租房者與房屋所有者之間通過 某些 中介機構 來交互、溝通。求職者和 用人企業之間 通過求職網站、獵頭 充當中介。 國與國之間的關系異常復雜,聯合國 可以算是一個 中介。等等
實際生活中,我們常常把 中介和 代理混為一談,因為有時候確實是 很難區分,但是設計模式中,我們不為,因為,我們需要滿足uml 的限制,當然,我們也不一定拘泥于 uml。 Agent 到底是代理 還是中介?討論這個問題沒有意義。
? ? ? ?示例有:協調者
? ? ? ?凡是以Mediator、Colleague、middleMan,Agent 結尾的類, 很可能就是用到了 中介 模式。當然,colleague的語義比較窄, 我們可能并不會使用這個詞,而是其他類似的詞。比如 同窗,同僚,co-author,co-worker,counterparter 。
2.19、備忘錄模式
? ? ? ?某些對象,你想保存下來, 但是對象的的內部狀態比較復雜, 你記不住, 不方便直接操作器狀態, 這個時候,我們可以使用 備忘錄(Memento)把它,“錄”下來,以防“備忘”,
? ? ? ?但是呢,備忘錄模式的 備忘錄本身沒有備忘的 功能,備忘的功能 由 負責人(Caretaker) 提供。所以說,備忘錄模式 跟我們 實際生活 用到的 備忘錄, 其實并不對等。 如果,非要對應起來的話,那么 “實際生活 用到的 備忘錄 ” 對應 備忘錄模式的Caretaker,但是“ 實際生活 用到的 備忘錄 ” 并沒有 restore 功能, 這個需要人來做,相對于是 save、 restore 功能分離了。
? ? ? ?場景: 需要撤銷動作的 對象
? ? ? ?示例有:游戲存檔
? ? ? ?Originator這個詞很少見,Mememto 也不多見,只要看到了它們,或者save 同時 restore 這個關鍵字, 很可能就是用到了 備忘錄模式。
2.20、觀察者模式
? ? ? ?觀察者模式又稱為發布-訂閱模式。
? ? ? ?觀察者? 太多了。觀察? 太常見的動作了!所以 觀察者模式 是我們時時刻刻都在用到的,只不過,用得太多,沒感覺了,向空氣一樣感覺不到它的存在了!比如吃飯,看飯下菜;你看電視,然后隨著劇情喜怒哀樂;開車根據路標指示做不同的操作;觀察明天的天氣,看看去哪里玩 合適;觀察研究不同股票的表現,決定買入還是賣出還是等待;隨心而動、隨性而為。。
? ? ? ?jdk是有提供 Observeable,Listener、消息隊列MQ。。。
? ? ? ?使用觀察者模式,天然的 就將 被觀察目標 和 觀察者 解耦了! 但是呢,我們實際軟件開發的過程中,并不是說 用眼睛觀察一下,看一下就完了(貌似只消耗了一些光子,沒有和 Subject產生 任何關系),并不是說 Subject和 Observer 完全沒有關系,Subject 是必須依賴Observer 的(一般來說,Subject對象 擁有 Observer 對象的集合)。
? ? ? ?另外,我們需要注意,實際軟件開發中 觀察者 是可以出現異常的(觀察到事件,但是處理發生了異常), 這個時候呢, 應該觀察者Observer 是不應該影響到 Subject 的繼續運行的,也就是說 Observer 需要處理所有異常, 而不應該 拋給Subject ?。
觀察者模式 和 監聽模式 幾乎是一個意思。 觀察者 就是 監聽器。
? ? ? ?觀察者模式 和 事件驅動 幾乎可以認為是 同義詞。觀察者模式 中雖然 沒有體現出來 事件, 但是 具體主題發生改變時,會notify 所有的 監聽者、觀察者, 給所有的觀察者發出通知, 這個通知 其實就是 可以認為是一個 事件。
? ? ? ?示例有:XxxListener
? ? ? ?凡是看到出現以Event、Listener結尾、或包含notify、update、observe、onXxxEvent方法 的類, 很可能就是用到了 觀察者模式。
2.21、狀態模式
? ? ? ?狀態模式 主要是把 對象的 狀態 獨立了出來,成了 狀態類, 每個狀態 都需要處理當前狀態下的 一些具體事情,對外表現不同的行為,所以 State 有一個 handle 方法, 每個ConcreteState 需要實現它。
? ? ? ?狀態模式 的核心要義并不是 狀態類, 而是 不同狀態之間的切換。Context 發起狀態切換,ConcreteState 完成具體的操作。
酒店房間Room有預定book、取消預定unbook、入住checkIn(可繼續細分為住滿,未住滿)、單人狀態等待雙人拼房、更換房間、退房checkOut等 必要的接口操作,有 空閑、預定、入住 等狀態, 不同狀態 對 客戶請求是不一樣的,空閑狀態是可以預定、直接入住的,但是不能退房。預定狀態可以 入住、更換房間、取消預定,但是不能退房,已入住狀態 不能再 預定、取消預定、入住, 只有一個退房操作。
? ? ? ?Context可以自己直接轉換狀態,但一般來說是交給State 去handle。具體來說,Room可以提供一個 setState 接口,或者提供 多個具體的接口:book,unbook, checkIn,checkOut等,顯然后者更加合理。
實際呢? 我看到使用更多的是Constants、Enum 這樣的常量、枚舉類,它們其實是可以轉換為狀態模式的。 ?而且如果 行為比較復雜, 改為 狀態模式 應該會更好一些。
? ? ? ?狀態模式 是否 可以完全的 取代 枚舉類 ? 當然是不能的,比如我一個很簡單的東西, 一個星期有7天,分別是Monday、Tuesday、Wednesday、Thursday、Friday、Saturday、Sunday, 這樣簡單的枚舉,并不涉及任何復雜的具體的 行為、操作, 用一個枚舉就蠻好的,星期幾一般不會用作為一個狀態,不會影響對象的行為。沒必要狀態模式。春夏秋冬,東南西北,都不是狀態。
? ? ? ?狀態模式 關鍵是狀態的選取,狀態一定是影響對象 對請求的響應的。關鍵要理解 狀態是否可以獨立出來,狀態改變對 對象行為的 影響。
? ? ? ?狀態模式 說白了就是 把一個很大的if else 語句塊,變成了 比較小的if else 語句塊,但是 if else 語句塊 仍然存在,不符合 開閉原則。
? ? ? ?示例有:銀行系統中的賬戶類設計
? ? ? ?凡是以State結尾的類, 很可能就是用到了 狀態模式。當然,這個是不一定的,我們可能會使用諸如 Status、Way、Method、Operation 這樣的次。
2.22、策略模式
? ? ? ?策略模式關鍵是需要理解 策略。這里的策略,并不是說指揮三軍打仗的 謀略,也不一定要 國家頒布了 新的房地產發展規劃指南 才能算是策略。而是說 做一件事情的 不同的方式,方法。 比如我吃飯,可以拿筷子吃,也可以直接手抓著吃,可以拿盤子盛著,可以拿碗,可以拿杯,也可以直接拿鍋; 去韓國旅行,可以坐飛機、鐵路、公路,海路等等,都可以是策略。
當然,我們還可以把策略理解為 算法。我需要排序一個數組,可以冒泡算法、可以選擇算法等等。 我們關注目的,只要目標達成了,就好了,不同的策略 給與我們不同的選擇,讓我們 靈活切換。
? ? ? ?示例有:電影票打折方案
? ? ? ?凡是以Strategy結尾的類, 很可能就是用到了 策略模式。或者algorithm 結尾。
2.23、模板方法模式
? ? ? ?如果你做了很多類似的,有一定重復工作的事情 但又不完全相同的,比較固定的操作流程, 而且以后也還要經常做,那么可以考慮 提取出來一個模板方法,把高層級的某些 操作順序、流程 固定化,然后 把具體的不同的工作 在實際操作的時候 去細化去完成,那么這就是 模板方法模式。
? ? ? ?模板方法模式 強調 模板。必須是能夠提取出來一個做事、操作的模板, 才比較合適。
? ? ? ?一些場景有:比如,每個人都要經歷 出生-學校讀書-工作-退休-去世 等過程,這些基本是固定的, 但是具體的操作非常不一樣。 當然這樣的例子 太寬泛了, 不容易進行細致討論。
比如,很多人的每天就是 起床-洗漱-早餐-坐公交/地鐵去公司-工作-午餐/午休-工作-坐公交/地鐵回家-晚餐-休息-睡覺 的這樣的routine , 對這些人,我們可以套用一個模板(當然,對于其他人,這個模板可能就不適用了)
? ? ? ?往細一點說,我們java 使用數據庫,都有固定的幾個步驟: 連接數據庫、獲取datasource、獲取connection、創建statement、執行sql、獲取結果resultset、關閉connection,除了 執行sql 按照具體情況很大不同之外,其他步驟基本是相同的,因為spring 為我們封裝了JDBCTemplate。等等。
? ? ? ?示例有:JDBCTemplat、銀行利息計算模塊
? ? ? ?凡是XxxTemplate這樣的 以Template結尾的類, 很可能就是用到了 模板方法模式。
2.24、訪問者模式
? ? ? ?現在 我們(作為游客,Vistor)去景區旅游。景區有很多景點(Element),每個景點呢需要收取不同的票價、有不同的景觀、有不同的接待(accept)方式,游客(Vistor)呢,可以選擇 任意一些景點,然后進行 游覽(visit)。我們可以都選擇獨立游或自駕游,但是這樣呢,景區會比較亂而 不好做規劃。我們可以選擇導游。導游((ObjectStructure))可以幫助我們完成這個過程。
? ? ? ?Element景點的一般操作是:制定門票價格、接受 游客 進入游玩(accept,accept方法的參數正是 Visitor 游客)
? ? ? ?ConcreteElement具體景點的作用是:設置具體的 門票價格,提供各種游玩場所和設施,接受游玩。
? ? ? ?Vistor游客的一般操作是:買票、訪問(visit ,visit 方法的參數正是 Element景點)某個 / 某些 景點 ... ;
? ? ? ?ConcreteVisitor具體游客的作用是:花實際的價錢買票,選擇具體的 景點,而不是全部。
? ? ? ?ObjectStructure導游的作用是:對象結構,包含了一個景區List (提供add、remove接口,可以動態增刪),能夠枚舉各個景點,然后 同樣提供 accept 接待訪問的接口, 然后 遍歷 list 以供游客訪問景區。
? ? ? ?這樣,我們可以 方便的增加 景點,和 游客。 好像沒有任何的問題。
? ? ? ?但是,uml 中 Vistor游客的訪問方法的參數 是具體景點ConcreteElement,而不是Element。可以認為ConcreteElementA是 歌劇表演、B是山水、C是舞蹈魔術 ,Vistor 做了這么一個限定,提供三個接口,任何游客可以訪問3個類型景點。 每個具體類型景點 需要標明自己是類型,(比如 門口標志 需要特定服裝才能進入、僅限兒童進入等等)也就是說,景區,為了牟利,并不管你是 兒童還是老人,都可以給你推薦 兒童樂園。 景區 盡量讓游客把每個景點都至少玩一遍。
? ? ? ?Vistor游客也可以分類,比如兒童、年輕人、老人,兒童有兒童的游玩方式、年輕人、老人游玩方式都不同。
?? ? ? ?還可以這樣分類: A兒童景點,B是年輕人景點,C是老人景點。
? ? ? ?這個模式有一點不好的是, 不管Vistor是否愿意,它需要把所有具體的Element的訪問接口準備好。當然,這僅僅是準備,并一定要實際的去訪問, 提供訪問的可能性。但這個是比較麻煩的,因為游客可能根本不需要去訪問它(不管怎么樣,景區還是要把 景點 推廣出去)。如果我們 增加了一個類型 景點,比如驚險刺激類的過山車,那么 所有的ConcreteElement都要做變化, 破壞了開閉原則。
? ? ? ?這個例子可能并不太好,實際情況,我們可以有那種不得不訪問的情況,比如 買房過程,一般來說,必須要去 房產銷售中心商談、去商品房的實際地點看看毛坯房、 去房管局登記 / 繳稅費 / 拿發票 等, 每一個步驟的接口 都是開放的, 買房者需要去準備(去實現之),雖然不一定馬上就去做(不一定立即調用 各個 visit 方法)。如果政府規定了買房必須去中國銀行 報備,那么 買房者還需要去準備 一個 訪問 中國銀行的接口。
? ? ? ?另外,訪問者模式 的訪問,不一定的 訪問,也可以是 交互、。。
? ? ? ?示例有:OA系統中員工數據匯總
? ? ? ?凡是看到 Visit、ObjectStructure, 很可能就是用到了 訪問者方法模式。
總結
- 上一篇: 论文浅尝 - ACL2020 | 用于回
- 下一篇: 设计模式之观察者模式在Listview中