1. 程式人生 > >看透設計模式-實踐與總結

看透設計模式-實踐與總結

23種設計模式,實際工作中,都是怎麼出現的呢? 有哪些示例呢? 

本文探討 生活 與 工作實踐中 的設計模式, 但這裡不想牽扯 UML了。

 

01、簡單工廠模式

簡單工廠模式 又稱為 靜態工廠模式

模式場景:在一個披薩店中,要根據不同客戶的口味,生產不同的披薩,如素食披薩、希臘披薩等披薩。

凡是看到一個工廠Factory,然後一個靜態方法,靜態方法一個引數,那麼很可能就是用到了 簡單工廠 模式。

 

02、工廠方法模式

—— client不需要知道具體產品,只需要知道具體工廠 即可。

在披薩例項中,如果我想根據地域的不同生產出不同口味的披薩,如紐約口味披薩,芝加哥口味披薩。如果利用簡單工廠模式,我們需要兩個不同的工廠,NYPizzaFactory、ChicagoPizzaFactory。在該地域中有很多的披薩店,他們並不想依照總店的製作流程來生成披薩,而是希望採用他們自己的製作流程。這個時候如果還使用簡單工廠模式,因為簡單工廠模式是將披薩的製作流程完全承包了。那麼怎麼辦?

這樣解決:將披薩的製作方法交給各個披薩店完成,但是他們只能提供製作完成的披薩,披薩的訂單處理仍然要交給披薩工廠去做。也就是說,我們將createPizza()方法放回到PizzaStore中,其他的部分還是保持不變。

示例有:日誌記錄器

凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以只能生產一個產品, 很可能就是用到了 工廠方法 模式。 

 

03、抽象工廠模式

依然是披薩店。為了要保證每家加盟店都能夠生產高質量的披薩,防止使用劣質的原料,我們打算建造一家生產原料的工廠,並將原料運送到各家加盟店。但是加盟店都位於不同的區域,比如紐約、芝加哥。紐約使用一組原料,芝加哥使用另一種原料。在這裡我們可以這樣理解,這些不同的區域組成了原料家族,每個區域實現了一個完整的原料家族。

 各個區域的加盟店的同一款產品是一個維度,是產品 等級結構

某個區域的加盟店的所有款產品是一個維度,是產品 族。

抽象工廠類是不會涉及 加盟店的,抽象產品類的所有產品構成 產品族。

 加盟店是具體的工廠類,是對抽象工廠類的實現。

凡是看到AbstractProduct、AbstractFactory、ConcretetFactory ,而且AbstractFactory 可以生產多個產品, 很可能就是用到了 抽象工廠 模式。 

 

===== 凡是看到Factory, 就要想到 工廠三兄弟 3個設計模式 ====

 

04、建造者模式

建造者模式構建複雜物件就像造汽車一樣,是一個一個元件一個一個步驟創建出來的,它允許使用者通過制定的物件型別和內容來建立他們,但是使用者並不需要知道這個複雜物件是如何構建的,它只需要明白通過這樣做我可以得到一個完整的複雜物件例項。

KFC裡面一般都有好幾種可供客戶選擇的套餐,它可以根據客戶所點的套餐,然後在後面做這些套餐,返回給客戶的事一個完整的、美好的套餐。(構造過程抽象統一為buildFood,buildDrink)

 示例有:遊戲角色設計

凡是看到出現以Director、Builder結尾、或包含build、 construct方法 的類, 很可能就是用到了 建造者模式。

 

05、原型模式

Ctrl+C、Ctrl+V、克隆、複製簡歷、複製Xxx。。。

 示例有:大同小異的工作週報、帶附件的週報

凡是看到出現以Prototype結尾、或包含clone、copy方法 的類, 很可能就是用到了 原型模式。 

 

06、單例模式

如果製造出多個例項,會導致很多問題產生的情況。

通常是重量級的類,比如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 這個詞, 但這個是常見的。是良好的實際。

 

07、介面卡模式

我們工作電壓11V的電腦,需要在 220V的 輸電電壓下 工作, 我們就需要 電源介面卡; 機器人想要模擬人的操作,那麼也需要進行 某些適配工作。

 示例有:沒有原始碼的演算法庫

Target 雖然常見,但是我們一般不會使用這個詞,而是直接使用我們實際的類的名稱。凡是看到出現以Adapter、Adaptee結尾、或包含request,specialRequest 方法 的類, 很可能就是用到了 介面卡 模式。儘管如此,我們實際中可能不會使用 request,specialRequest 這樣的詞, 而是直接、具體的 介面名字。

 

08、橋接模式

假設圖形類有兩個關鍵屬性: 形狀、顏色。我們有 正方形、長方形、圓形,有三種顏色:白色、灰色、黑色,那麼我們可以畫出3*3=9中圖形。 那麼 我們是不是就需要九個 class類呢? 不需要的,通過橋接模式,我們只需要 3+3 = 6 個類。

可以想象,如果形狀、顏色 更多,那麼 橋接模式 就非常有效的減少了類的數量。

橋接模式 好像和 裝飾模式有異曲同工之妙,它們都可以有效的減少了類的數量, 防止類數量爆炸。 但是這裡的例子中,裝飾模式是不合適的, 因為 形狀、顏色 是兩個獨立的屬性,沒有任何相關關係的。

橋接模式 是對 不同屬性, 從不同維度的 拆分。屬性之間 僅僅是一個 聚合關係。

裝飾模式 是對 不同行為,圍繞某個主要行為 進行 裝飾。需要有繼承or實現關係。

 示例有:跨平臺影象瀏覽系統

凡是看到出現以RefinedAbstraction、Implementor結尾 的類, 很可能就是用到了 橋接模式。 雖然實際中 很可能不會使用 RefinedAbstraction、Implementor 這樣的詞來命名我們的類名,但是, 仍然可以做一個判斷。

 

09、組合模式

凡是涉及檔案系統的瀏覽遍歷、操作等的, 比如 防毒軟體對 檔案系統進行防毒。

語法樹,比如XML,HTML,JSON 常常存在 “容器” 元素, 就很適合 組合模式  。比如 HTML 語法樹,我們可以把 div 當做一個 容器,div 可以巢狀各種子元素。

 示例有:Configuration、Bean、Component

凡是看到出現以Composite、Leaf、combination、assembly、group結尾、或包含filter、 handle、intercept 方法 以及 add、remove、getChild 方法 的類, 很可能就是用到了 裝飾器模式。 

 

10、裝飾器模式

需要動態地給一個物件增加功能,這些功能也可以動態地被撤銷。  當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。

裝飾者模式的關鍵詞, 一定是“動態、靈活”, 通過繼承,我們可以做很多事情,但是顯得十分臃腫,十分不動態,類數量(m*n)爆炸。裝飾者模式可以避免這些, 將類數量 變化 m + n

其實,裝飾模式和介面卡模式都是“包裝模式(Wrapper Pattern)”。 另外代理模式,好像也有包裝 功能,但是要求、側重點是不同的。

示例有:圖形介面構件庫

凡是看到出現以Decorator、Filter、Interceptor 、Wrapper 結尾、或包含filter、 handle、intercept 方法 的類, 很可能就是用到了 裝飾器模式。

 

 

11、外觀/門面模式

非常籠統而寬泛的模式

 示例有:B/S系統的首頁等等

凡是看到出現以Facade、System、Manager 結尾 的類, 很可能就是用到了 外觀/門面模式。 沒錯,這裡有個Manager ,Manager 是一個範疇很大的詞。

 

12、享元模式

其實就是 對一系列類似的 物件, 實現了 單例模式, 然後統一管理。

java String物件,圍棋的棋子

 示例有:ThreadLocal,Redis快取。http、redis、mq、kafka、資料庫 連線池?

凡是看到出現以Flyweight 結尾 的類, 很可能就是用到了享元模式。實踐中,我們可能不會使用Flyweight這樣的詞,而是具體的 “ 元 ” 的類名, 而判斷是否是 享元, 關鍵是 判斷是否有一個 map 存在, 其 get 方法是否會判斷“元” 是否已經存在,是否包含了 put 操作。 

 

13、代理模式

web 代理伺服器、Nginx反向代理,JDK動態代理,CGlib 動態代理

1、 遠端代理:為一個物件在不同的地址空間提供區域性代表。這樣可以隱藏一個物件存在於不同地址空間的事實。

2、 虛擬代理:通過使用過一個小的物件代理一個大物件。這樣就可以減少系統的開銷。

3、 保護代理:用來控制對真實物件的訪問許可權。

示例有:收費商務資訊查詢系統

凡是以Proxy 結尾,或看到出現包含preRequest,postRequest 等 preXxx、postXxx 方法 的類,或看到 InvocationHandler, 很可能就是用到了代理模式。 

 

14、職責鏈模式

凡是那些涉及 不同等級層次的 操作、流程,不同等級有不同許可權、功能,或需要 層層審批。那麼就適合。

示例有:採購單的分級審批、請假流程、報銷審批流程。。。

凡是看到出現以Chain、pipeline 結尾、或包含handler,handleRequest 方法 的類, 很可能就是用到了責任鏈模式。

 

15、命令模式

Invoker 本來可以直接呼叫Receiver, 但是Invoker 覺得麻煩,不想、不需或不能 管太多 ,那麼我們引入一個 Command 中間層,那麼 就實現瞭解耦。

一些場景:控制面板、遙控器

示例有:自定義功能鍵、撤銷操作、巨集命令

凡是看到出現以Command、Order 、Invoker、Receiver 結尾、或包含execute、call、action 方法 的類, 很可能就是用到了 命令模式。

 

16、直譯器模式

語法分析解釋權、抽象語法樹,翻譯機。對不懂的外國語進行 翻譯。等等

示例有:機器人控制程式

凡是看到出現以Expression/ translate/ interpret 結尾、或包含 translate/ interpret 方法 的類, 很可能就是用到了 直譯器模式。

 

17、迭代器模式

Aggregate和 Iterator 分離。為什麼需要分離? Iterator 不需要知道Aggregate的資料是怎麼來的, 可以按照需要的 方式進行遍歷。 比如對電視機的各個有限電視臺的遍歷,我們可以 按照 各個省、地方電視臺的順序,一個省的所有電視臺遍歷完了之後,再其他省; 也可以按照 先遍歷所有省的 娛樂頻道,然後遍歷所有省的 經濟頻道,然後。。

HaspMap的 迭代器。

 示例有:JDK內建迭代器、銷售管理系統中資料的遍歷
凡是看到出現以Iterator、Loop 結尾、或包含hasNext,next,hasMore方法 的類, 很可能就是用到了 迭代器模式。

 

18、中介者模式

中介,就是我們平常所理解的 中介。適合某些 雙方/多方 不宜 直接對話的情況。 中介者, 當然,需要一定的 權威性、權力、公信度。

同事之間 雖然可以直接1對1,溝通,但某些時刻需要通過部門負責人來 中介、協調。同學之間 需要通過 輔導員、班主任來溝通、 統一領導、指揮。男方和 女方 通過婚介所 進行聯誼。 租房者與房屋所有者之間通過 某些 中介機構 來互動、溝通。求職者和 用人企業之間 通過求職網站、獵頭 充當中介。 國與國之間的關係異常複雜,聯合國 可以算是一個 中介。等等

實際生活中,我們常常把 中介和 代理混為一談,因為有時候確實是 很難區分,但是設計模式中,我們不為,因為,我們需要滿足uml 的限制,當然,我們也不一定拘泥於 uml。 Agent 到底是代理 還是中介?討論這個問題沒有意義。

示例有:協調者

凡是以Mediator、Colleague、middleMan,Agent 結尾的類, 很可能就是用到了 中介 模式。當然,colleague的語義比較窄, 我們可能並不會使用這個詞,而是其他類似的詞。比如 同窗,同僚,co-author,co-worker,counterparter 。

 

19、備忘錄模式

某些物件,你想儲存下來, 但是物件的的內部狀態比較複雜, 你記不住, 不方便直接操作器狀態, 這個時候,我們可以使用 備忘錄(Memento)把它,“錄”下來,以防“備忘”,

但是呢,備忘錄模式的 備忘錄本身沒有備忘的 功能,備忘的功能 由 負責人(Caretaker) 提供。  所以說,備忘錄模式 跟我們 實際生活 用到的 備忘錄, 其實並不對等。 如果,非要對應起來的話,那麼 “實際生活 用到的 備忘錄 ” 對應 備忘錄模式的Caretaker,但是“ 實際生活 用到的 備忘錄 ” 並沒有 restore 功能, 這個需要人來做,相對於是 save、 restore 功能分離了。

場景: 需要撤銷動作的 物件

示例有:遊戲存檔

Originator這個詞很少見,Mememto 也不多見,只要看到了它們,或者save 同時 restore 這個關鍵字, 很可能就是用到了 備忘錄模式。。

 

20、觀察者模式

觀察者模式又稱為釋出-訂閱模式。

觀察者? 太多了。觀察? 太常見的動作了!所以 觀察者模式 是我們時時刻刻都在用到的,只不過,用得太多,沒感覺了,向空氣一樣感覺不到它的存在了!比如吃飯,看飯下菜;你看電視,然後隨著劇情喜怒哀樂;開車根據路標指示做不同的操作;觀察明天的天氣,看看去哪裡玩 合適;觀察研究不同股票的表現,決定買入還是賣出還是等待;隨心而動、隨性而為。。

jdk是有提供 Observeable,Listener、訊息佇列MQ。。。

使用觀察者模式,天然的 就將 被觀察目標 和 觀察者 解耦了! 但是呢,我們實際軟體開發的過程中,並不是說 用眼睛觀察一下,看一下就完了(貌似只消耗了一些光子,沒有和 Subject產生 任何關係),並不是說 Subject和 Observer 完全沒有關係,Subject 是必須依賴Observer 的(一般來說,Subject物件 擁有 Observer 物件的集合)。

另外,我們需要注意,實際軟體開發中 觀察者 是可以出現異常的(觀察到事件,但是處理髮生了異常), 這個時候呢, 應該觀察者Observer 是不應該影響到 Subject 的繼續執行的,也就是說 Observer 需要處理所有異常, 而不應該 拋給Subject  。

觀察者模式 和 監聽模式 幾乎是一個意思。 觀察者 就是 監聽器。

觀察者模式 和 事件驅動 幾乎可以認為是 同義詞。觀察者模式 中雖然 沒有體現出來 事件, 但是 具體主題發生改變時,會notify 所有的 監聽者、觀察者, 給所有的觀察者發出通知, 這個通知 其實就是 可以認為是一個 事件。

示例有:XxxListener

凡是看到出現以Event、Listener結尾、或包含notify、update、observe、onXxxEvent方法 的類, 很可能就是用到了 觀察者模式。

 

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 這樣的次。

 

22、策略模式

策略模式關鍵是需要理解 策略。這裡的策略,並不是說指揮三軍打仗的 謀略,也不一定要 國家頒佈了 新的房地產發展規劃指南 才能算是策略。而是說 做一件事情的 不同的方式,方法。 比如我吃飯,可以拿筷子吃,也可以直接手抓著吃,可以拿盤子盛著,可以拿碗,可以拿杯,也可以直接拿鍋; 去韓國旅行,可以坐飛機、鐵路、公路,海路等等,都可以是策略。

當然,我們還可以把策略理解為 演算法。我需要排序一個數組,可以冒泡演算法、可以選擇演算法等等。 我們關注目的,只要目標達成了,就好了,不同的策略 給與我們不同的選擇,讓我們 靈活切換。

示例有:電影票打折方案

凡是以Strategy結尾的類, 很可能就是用到了 策略模式。或者algorithm 結尾。

 

23、模板方法模式

如果你做了很多類似的,有一定重複工作的事情 但又不完全相同的,比較固定的操作流程, 而且以後也還要經常做,那麼可以考慮 提取出來一個模板方法,把高層級的某些 操作順序、流程 固定化,然後 把具體的不同的工作 在實際操作的時候 去細化去完成,那麼這就是 模板方法模式。

模板方法模式 強調 模板。必須是能夠提取出來一個做事、操作的模板, 才比較合適。

一些場景有:

比如,每個人都要經歷 出生-學校讀書-工作-退休-去世 等過程,這些基本是固定的, 但是具體的操作非常不一樣。 當然這樣的例子 太寬泛了, 不容易進行細緻討論。

比如,很多人的每天就是 起床-洗漱-早餐-坐公交/地鐵去公司-工作-午餐/午休-工作-坐公交/地鐵回家-晚餐-休息-睡覺 的這樣的routine , 對這些人,我們可以套用一個模板(當然,對於其他人,這個模板可能就不適用了)

往細一點說,我們java 使用資料庫,都有固定的幾個步驟: 連線資料庫、獲取datasource、獲取connection、建立statement、執行sql、獲取結果resultset、關閉connection,除了 執行sql 按照具體情況很大不同之外,其他步驟基本是相同的,因為spring 為我們封裝了JDBCTemplate。等等。

示例有:JDBCTemplat、銀行利息計算模組

凡是XxxTemplate這樣的 以Template結尾的類, 很可能就是用到了 模板方法模式。

 

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, 很可能就是用到了 訪問者方法模式。

 

============================================ THE END,THANK YOU ALL ~! ============================================

參考:

https://blog.csdn.net/LoveLion

https://www.cnblogs.com/chenssy/p/3357683.html