如何理解這6種常見設(shè)計(jì)模式?
設(shè)計(jì)模式能夠幫助我們優(yōu)化代碼結(jié)構(gòu),讓代碼更優(yōu)雅靈活。有哪些常見的設(shè)計(jì)模式?如何合理運(yùn)用?本文分享作者對(duì)工廠模式、單例模式、裝飾模式、策略模式、代理模式和觀察者模式的理解,介紹每種模式的模式結(jié)構(gòu)、優(yōu)缺點(diǎn)、適用場(chǎng)景、注意實(shí)現(xiàn)及代碼實(shí)現(xiàn)。
一 前言
最近在改造一些歷史的代碼,發(fā)現(xiàn)一個(gè)很明顯的特點(diǎn),大部分代碼是記敘文,按照事件的發(fā)展過程將故事平鋪直敘的講解出來。
這種方式的好處是比較符合人類的思維習(xí)慣,一條主線講到底,代碼閱讀起來沒有太大難度,只要順著藤就能摸到瓜,但是缺點(diǎn)也很明顯,一旦故事線中需要插入一些新的元素,比如:加入一個(gè)新的人物角色、新的時(shí)間線,都會(huì)需要大量更改故事線以配合這個(gè)新元素的融入,甚至對(duì)原有文章造成破壞性的影響。
為了解決這個(gè)問題,人們總結(jié)出了很多種文章結(jié)構(gòu),例如:總-分結(jié)構(gòu),并列結(jié)構(gòu),總-分-總結(jié)構(gòu)等等,有了這些結(jié)構(gòu),在加入新元素的時(shí)候,甚至不必考慮新元素與原故事情節(jié)的關(guān)聯(lián)性,直接單拉一個(gè)分支故事線獨(dú)立去講就好了,只要能夠在整體故事結(jié)束前,與匯聚到主線故事就可以了(是不是很像git?)。
在軟件開發(fā)領(lǐng)域,也有很多這樣的非常有用的實(shí)踐總結(jié),我們稱之為設(shè)計(jì)模式。對(duì)于設(shè)計(jì)模式,大家都不陌生,隨便找個(gè)人,估計(jì)都能講出N個(gè)設(shè)計(jì)模式來,但是除了這些設(shè)計(jì)模式的概念,很多人不知道如何靈活運(yùn)用這些設(shè)計(jì)模式。所以借這篇文章和大家共同學(xué)習(xí)設(shè)計(jì)模式的思想。
二 理解設(shè)計(jì)模式
我盡量用最通俗易懂的示例和語(yǔ)言來講述我理解的設(shè)計(jì)模式,希望能對(duì)大家有所幫助。
另外也無(wú)需精通所有的設(shè)計(jì)模式,只要能夠融匯貫通常見的設(shè)計(jì)模式,就能讓你的代碼變得優(yōu)雅。就像程咬金只會(huì)三板斧,但是熟練度無(wú)人能及,照樣能橫行天下。
1 工廠模式(Factory)
簡(jiǎn)單工廠(Simple Factory)
小明追妹子的時(shí)候,請(qǐng)她喝了不少咖啡,她愛喝卡布奇諾,每次去咖啡店,只要跟服務(wù)員說“來杯卡布奇諾”就行了,雖然各家的口味有些不同,但是不管是星爸爸還是Costa,都能夠提供卡布奇諾這種咖啡。這里的星爸爸和Costa就是生產(chǎn)咖啡的工廠。
(1)簡(jiǎn)單工廠模式結(jié)構(gòu)
簡(jiǎn)單工廠模式包含如下角色:
- Factory:工廠角色-負(fù)責(zé)實(shí)現(xiàn)創(chuàng)建所有實(shí)例的內(nèi)部邏輯.
 - Product:抽象產(chǎn)品角色-是所創(chuàng)建的所有對(duì)象的父類,負(fù)責(zé)描述所有實(shí)例所共有的公共接口。
 - ConcreteProduct:具體產(chǎn)品角色-是創(chuàng)建目標(biāo),所有創(chuàng)建的對(duì)象都充當(dāng)這個(gè)角色的某個(gè)具體類的實(shí)例。
 
結(jié)構(gòu)圖:
時(shí)序圖:
(2)優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):客戶類和工廠類分開。消費(fèi)者任何時(shí)候需要某種產(chǎn)品,只需向工廠請(qǐng)求即可。消費(fèi)者無(wú)須修改就可以接納新產(chǎn)品。
 - 缺點(diǎn):是當(dāng)產(chǎn)品修改時(shí),工廠類也要做相應(yīng)的修改。
 
工廠方法(Factory Method)
以前經(jīng)常帶老婆去優(yōu)衣庫(kù)(簡(jiǎn)單工廠)買衣服,就那么多款式,逛的次數(shù)多了,她就煩了。后來我改變策略,帶老婆去逛商場(chǎng)(抽象工廠),商場(chǎng)里有各式品牌的店鋪,不用我管,她自己就能逛上一整天。
區(qū)別于簡(jiǎn)單工廠,核心工廠類(商場(chǎng))不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體創(chuàng)建的工作交給子類(服裝店)去做,成為一個(gè)抽象工廠角色,僅負(fù)責(zé)給出具體工廠類必須實(shí)現(xiàn)的接口(門店),而不接觸哪一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化這種細(xì)節(jié)。
(1)工廠方法模式結(jié)構(gòu)
工廠方法模式包含如下角色:
- Product:抽象產(chǎn)品
 - ConcreteProduct:具體產(chǎn)品
 - Factory:抽象工廠
 - ConcreteFactory:具體工廠
 
結(jié)構(gòu)圖:
時(shí)序圖:
工廠模式總結(jié)
(1)適用場(chǎng)景
輸出的產(chǎn)品是標(biāo)準(zhǔn)品,誰(shuí)來做都可以。
(2)舉例
常見的數(shù)據(jù)庫(kù)連接工廠,SqlSessionFactory,產(chǎn)品是一個(gè)數(shù)據(jù)庫(kù)連接,至于是oracle提供的,還是mysql提供的,我并不需要關(guān)心,因?yàn)槎寄茏屛彝ㄟ^sql來操作數(shù)據(jù)。
(3)注意事項(xiàng)
項(xiàng)目初期,軟件結(jié)構(gòu)和需求都沒有穩(wěn)定下來時(shí),不建議使用此模式,因?yàn)槠淞觿?shì)也很明顯,增加了代碼的復(fù)雜度,增加了調(diào)用層次,增加了內(nèi)存負(fù)擔(dān)。所以要注意防止模式的濫用。
(4)簡(jiǎn)單實(shí)現(xiàn)
- package FactoryMethod;
 - public class FactoryPattern
 - {
 - public static void main(String[] args)
 - {
 - Factory factory = new ConcreteFactoryA();
 - Product product = factory.createProduct();
 - product.use();
 - }
 - }
 - //抽象產(chǎn)品:提供了產(chǎn)品的接口
 - interface Product
 - {
 - public void use();
 - }
 - //具體產(chǎn)品A:實(shí)現(xiàn)抽象產(chǎn)品中的抽象方法
 - class ConcreteProductA implements Product
 - {
 - public void use()
 - {
 - System.out.println("具體產(chǎn)品A顯示...");
 - }
 - }
 - //具體產(chǎn)品B:實(shí)現(xiàn)抽象產(chǎn)品中的抽象方法
 - class ConcreteProductB implements Product
 - {
 - public void use()
 - {
 - System.out.println("具體產(chǎn)品B顯示...");
 - }
 - }
 - //抽象工廠:提供了廠品的生成方法
 - interface Factory
 - {
 - public Product createProduct();
 - }
 - //具體工廠A:實(shí)現(xiàn)了廠品的生成方法
 - class ConcreteFactoryA implements AbstractFactory
 - {
 - public Product createProduct()
 - {
 - System.out.println("具體工廠A生成-->具體產(chǎn)品A.");
 - return new ConcreteProductA();
 - }
 - }
 - //具體工廠B:實(shí)現(xiàn)了廠品的生成方法
 - class ConcreteFactoryB implements AbstractFactory
 - {
 - public Product createProduct()
 - {
 - System.out.println("具體工廠B生成-->具體產(chǎn)品B.");
 - return new ConcreteProductB();
 - }
 - }
 
2 單例模式(Singleton)
韋小寶有7個(gè)老婆,但是每個(gè)都只有他這一個(gè)老公,他的所有老婆叫老公時(shí),指的都是他,他就是一個(gè)單例。
單例模式結(jié)構(gòu)
單例模式包含如下角色:
- Singleton:?jiǎn)卫?/li>
 
結(jié)構(gòu)圖:
時(shí)序圖:
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):全局只有一個(gè)實(shí)例,便于統(tǒng)一控制,同時(shí)減少了系統(tǒng)資源開銷。
 - 缺點(diǎn):沒有抽象層,擴(kuò)展困難。
 
應(yīng)用場(chǎng)景
適合需要做全局統(tǒng)一控制的場(chǎng)景,例如:全局唯一的編碼生成器。
注意事項(xiàng)
只對(duì)外提供公共的getInstance方法,不提供任何公共構(gòu)造函數(shù)。
簡(jiǎn)單實(shí)現(xiàn)
- public class Singleton
 - {
 - private static volatile Singleton instance=null; //保證 instance 在所有線程中同步
 - private Singleton(){} //private 避免類在外部被實(shí)例化
 - public static synchronized Singleton getInstance()
 - {
 - //getInstance 方法前加同步
 - if(instance == null)
 - {
 - instance = new Singleton();
 - }
 - return instance;
 - }
 - }
 
3 裝飾模式(Decorator)
大學(xué)畢業(yè),想要送給室友一個(gè)有紀(jì)念意義的禮物,就找到一張大家的合照,在上面寫上“永遠(yuǎn)的兄弟!”,然后拿去禮品店裝了個(gè)相框,再包上禮盒。這里的我和禮品店都是裝飾器,都沒有改變照片本身,卻都讓照片變得更適合作為禮物送人。
裝飾模式結(jié)構(gòu)
裝飾模式包含如下角色:
- Component:抽象構(gòu)件
 - ConcreteComponent:具體構(gòu)件
 - Decorator:抽象裝飾類
 - ConcreteDecorator:具體裝飾類
 
結(jié)構(gòu)圖:
時(shí)序圖:
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):比繼承更加靈活(繼承是耦合度很大的靜態(tài)關(guān)系),可以動(dòng)態(tài)的為對(duì)象增加職責(zé),可以通過使用不同的裝飾器組合為對(duì)象擴(kuò)展N個(gè)新功能,而不會(huì)影響到對(duì)象本身。
 - 缺點(diǎn):當(dāng)一個(gè)對(duì)象的裝飾器過多時(shí),會(huì)產(chǎn)生很多的裝飾類小對(duì)象和裝飾組合策略,增加系統(tǒng)復(fù)雜度,增加代碼的閱讀理解成本。
 
適用場(chǎng)景
- 適合需要(通過配置,如:diamond)來動(dòng)態(tài)增減對(duì)象功能的場(chǎng)景。
 - 適合一個(gè)對(duì)象需要N種功能排列組合的場(chǎng)景(如果用繼承,會(huì)使子類數(shù)量爆炸式增長(zhǎng))
 
注意事項(xiàng)
- 一個(gè)裝飾類的接口必須與被裝飾類的接口保持相同,對(duì)于客戶端來說無(wú)論是裝飾之前的對(duì)象還是裝飾之后的對(duì)象都可以一致對(duì)待。
 - 盡量保持具體構(gòu)件類Component作為一個(gè)“輕”類,也就是說不要把太多的邏輯和狀態(tài)放在具體構(gòu)件類中,可以通過裝飾類。
 
簡(jiǎn)單實(shí)現(xiàn)
- package decorator;
 - public class DecoratorPattern
 - {
 - public static void main(String[] args)
 - {
 - Component component = new ConcreteComponent();
 - component.operation();
 - System.out.println("---------------------------------");
 - Component decorator = new ConcreteDecorator(component);
 - decorator.operation();
 - }
 - }
 - //抽象構(gòu)件角色
 - interface Component
 - {
 - public void operation();
 - }
 - //具體構(gòu)件角色
 - class ConcreteComponent implements Component
 - {
 - public ConcreteComponent()
 - {
 - System.out.println("創(chuàng)建具體構(gòu)件角色");
 - }
 - public void operation()
 - {
 - System.out.println("調(diào)用具體構(gòu)件角色的方法operation()");
 - }
 - }
 - //抽象裝飾角色
 - class Decorator implements Component
 - {
 - private Component component;
 - public Decorator(Component component)
 - {
 - this.component=component;
 - }
 - public void operation()
 - {
 - component.operation();
 - }
 - }
 - //具體裝飾角色
 - class ConcreteDecorator extends Decorator
 - {
 - public ConcreteDecorator(Component component)
 - {
 - super(component);
 - }
 - public void operation()
 - {
 - super.operation();
 - addBehavior();
 - }
 - public void addBehavior()
 - {
 - System.out.println("為具體構(gòu)件角色增加額外的功能addBehavior()");
 - }
 - }
 
4 策略模式(Strategy)
男生追妹子時(shí),一般都會(huì)用到這種模式,常見的策略有這些:約會(huì)吃飯;看電影;看演唱會(huì);逛街;去旅行……,雖然做的事情不同,但可以相互替換,唯一的目標(biāo)都是捕獲妹子的芳心。
策略模式結(jié)構(gòu)
- Context: 環(huán)境類
 - Strategy: 抽象策略類
 - ConcreteStrategy: 具體策略類
 
結(jié)構(gòu)圖:
時(shí)序圖:
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):策略模式提供了對(duì)“開閉原則”的完美支持,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為。干掉復(fù)雜難看的if-else。
 - 缺點(diǎn):調(diào)用時(shí),必須提前知道都有哪些策略模式類,才能自行決定當(dāng)前場(chǎng)景該使用何種策略。
 
試用場(chǎng)景
一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種可替換算法中選擇一種。不希望使用者關(guān)心算法細(xì)節(jié),將具體算法封裝進(jìn)策略類中。
注意事項(xiàng)
一定要在策略類的注釋中說明該策略的用途和適用場(chǎng)景。
簡(jiǎn)單實(shí)現(xiàn)
- package strategy;
 - public class StrategyPattern
 - {
 - public static void main(String[] args)
 - {
 - Context context = new Context();
 - Strategy strategyA = new ConcreteStrategyA();
 - context.setStrategy(strategyA);
 - context.algorithm();
 - System.out.println("-----------------");
 - Strategy strategyB = new ConcreteStrategyB();
 - context.setStrategy(strategyB);
 - context.algorithm();
 - }
 - }
 - //抽象策略類
 - interface Strategy
 - {
 - public void algorithm(); //策略方法
 - }
 - //具體策略類A
 - class ConcreteStrategyA implements Strategy
 - {
 - public void algorithm()
 - {
 - System.out.println("具體策略A的策略方法被訪問!");
 - }
 - }
 - //具體策略類B
 - class ConcreteStrategyB implements Strategy
 - {
 - public void algorithm()
 - {
 - System.out.println("具體策略B的策略方法被訪問!");
 - }
 - }
 - //環(huán)境類
 - class Context
 - {
 - private Strategy strategy;
 - public Strategy getStrategy()
 - {
 - return strategy;
 - }
 - public void setStrategy(Strategy strategy)
 - {
 - this.strategy=strategy;
 - }
 - public void algorithm()
 - {
 - strategy.algorithm();
 - }
 - }
 
5 代理模式(Proxy)
淘寶店客服總是會(huì)收到非常多的重復(fù)問題,例如:有沒有現(xiàn)貨?什么時(shí)候發(fā)貨?發(fā)什么快遞?大量回答重復(fù)性的問題太煩了,于是就出現(xiàn)了小蜜機(jī)器人,他來幫客服回答那些已知的問題,當(dāng)碰到小蜜無(wú)法解答的問題時(shí),才會(huì)轉(zhuǎn)到人工客服。這里的小蜜機(jī)器人就是客服的代理。
代理模式結(jié)構(gòu)
代理模式包含如下角色:
- Subject: 抽象主題角色
 - Proxy: 代理主題角色
 - RealSubject: 真實(shí)主題角色
 
結(jié)構(gòu)圖:
時(shí)序圖:
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):代理可以協(xié)調(diào)調(diào)用方與被調(diào)用方,降低了系統(tǒng)的耦合度。根據(jù)代理類型和場(chǎng)景的不同,可以起到控制安全性、減小系統(tǒng)開銷等作用。
 - 缺點(diǎn):增加了一層代理處理,增加了系統(tǒng)的復(fù)雜度,同時(shí)可能會(huì)降低系統(tǒng)的相應(yīng)速度。
 
試用場(chǎng)景
理論上可以代理任何對(duì)象,常見的代理模式有:
- 遠(yuǎn)程(Remote)代理:為一個(gè)位于不同的地址空間的對(duì)象提供一個(gè)本地的代理對(duì)象,這個(gè)不同的地址空間可以是在同一臺(tái)主機(jī)中,也可是在另一臺(tái)主機(jī)中,遠(yuǎn)程代理又叫做大使(Ambassador)。
 - 虛擬(Virtual)代理:如果需要?jiǎng)?chuàng)建一個(gè)資源消耗較大的對(duì)象,先創(chuàng)建一個(gè)消耗相對(duì)較小的對(duì)象來表示,真實(shí)對(duì)象只在需要時(shí)才會(huì)被真正創(chuàng)建。
 - Copy-on-Write代理:它是虛擬代理的一種,把復(fù)制(克隆)操作延遲到只有在客戶端真正需要時(shí)才執(zhí)行。一般來說,對(duì)象的深克隆是一個(gè)開銷較大的操作,Copy-on-Write代理可以讓這個(gè)操作延遲,只有對(duì)象被用到的時(shí)候才被克隆。
 - 保護(hù)(Protect or Access)代理:控制對(duì)一個(gè)對(duì)象的訪問,可以給不同的用戶提供不同級(jí)別的使用權(quán)限。
 - 緩沖(Cache)代理:為某一個(gè)目標(biāo)操作的結(jié)果提供臨時(shí)的存儲(chǔ)空間,以便多個(gè)客戶端可以共享這些結(jié)果。
 - 防火墻(Firewall)代理:保護(hù)目標(biāo)不讓惡意用戶接近。
 - 同步化(Synchronization)代理:使幾個(gè)用戶能夠同時(shí)使用一個(gè)對(duì)象而沒有沖突。
 - 智能引用(Smart Reference)代理:當(dāng)一個(gè)對(duì)象被引用時(shí),提供一些額外的操作,如將此對(duì)象被調(diào)用的次數(shù)記錄下來等。
 
簡(jiǎn)單實(shí)現(xiàn)
- package proxy;
 - public class ProxyPattern
 - {
 - public static void main(String[] args)
 - {
 - Proxy proxy = new Proxy();
 - proxy.request();
 - }
 - }
 - //抽象主題
 - interface Subject
 - {
 - void request();
 - }
 - //真實(shí)主題
 - class RealSubject implements Subject
 - {
 - public void request()
 - {
 - System.out.println("訪問真實(shí)主題方法...");
 - }
 - }
 - //代理
 - class Proxy implements Subject
 - {
 - private RealSubject realSubject;
 - public void request()
 - {
 - if (realSubject==null)
 - {
 - realSubject=new RealSubject();
 - }
 - preRequest();
 - realSubject.request();
 - afterRequest();
 - }
 - public void preRequest()
 - {
 - System.out.println("訪問真實(shí)主題之前的預(yù)處理。");
 - }
 - public void afterRequest()
 - {
 - System.out.println("訪問真實(shí)主題之后的后續(xù)處理。");
 - }
 - }
 
6 觀察者模式(Observer)
出差在外,想了解孩子在家的情況,這時(shí)候只要加入“相親相愛一家人”群,老爸老媽會(huì)經(jīng)常把孩子的照片和視頻發(fā)到群里,你要做的就是作為一個(gè)觀察者,刷一刷群里的信息就能夠了解一切了。
觀察者模式結(jié)構(gòu)
觀察者模式包含如下角色:
- Subject:目標(biāo)
 - ConcreteSubject:具體目標(biāo)
 - Observer:觀察者
 - ConcreteObserver:具體觀察者
 
結(jié)構(gòu)圖:
時(shí)序圖:
優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):將復(fù)雜的串行處理邏輯變?yōu)閱卧莫?dú)立處理邏輯,被觀察者只是按照自己的邏輯發(fā)出消息,不用關(guān)心誰(shuí)來消費(fèi)消息,每個(gè)觀察者只處理自己關(guān)心的內(nèi)容。邏輯相互隔離帶來簡(jiǎn)單清爽的代碼結(jié)構(gòu)。
 - 缺點(diǎn):觀察者較多時(shí),可能會(huì)花費(fèi)一定的開銷來發(fā)消息,但這個(gè)消息可能僅一個(gè)觀察者消費(fèi)。
 
適用場(chǎng)景
適用于一對(duì)多的的業(yè)務(wù)場(chǎng)景,一個(gè)對(duì)象發(fā)生變更,會(huì)觸發(fā)N個(gè)對(duì)象做相應(yīng)處理的場(chǎng)景。例如:訂單調(diào)度通知,任務(wù)狀態(tài)變化等。
注意事項(xiàng)
避免觀察者與被觀察者之間形成循環(huán)依賴,可能會(huì)因此導(dǎo)致系統(tǒng)崩潰。
簡(jiǎn)單實(shí)現(xiàn)
- package observer;
 - import java.util.*;
 - public class ObserverPattern
 - {
 - public static void main(String[] args)
 - {
 - Subject subject = new ConcreteSubject();
 - Observer obsA = new ConcreteObserverA();
 - Observer obsb = new ConcreteObserverB();
 - subject.add(obsA);
 - subject.add(obsB);
 - subject.setState(0);
 - }
 - }
 - //抽象目標(biāo)
 - abstract class Subject
 - {
 - protected List<Observer> observerList = new ArrayList<Observer>();
 - //增加觀察者方法
 - public void add(Observer observer)
 - {
 - observers.add(observer);
 - }
 - //刪除觀察者方法
 - public void remove(Observer observer)
 - {
 - observers.remove(observer);
 - }
 - public abstract void notify(); //通知觀察者方法
 - }
 - //具體目標(biāo)
 - class ConcreteSubject extends Subject
 - {
 - private Integer state;
 - public void setState(Integer state){
 - this.state = state;
 - // 狀態(tài)改變通知觀察者
 - notify();
 - }
 - public void notify()
 - {
 - System.out.println("具體目標(biāo)狀態(tài)發(fā)生改變...");
 - System.out.println("--------------");
 - for(Observer obs:observers)
 - {
 - obs.process();
 - }
 - }
 - }
 - //抽象觀察者
 - interface Observer
 - {
 - void process(); //具體的處理
 - }
 - //具體觀察者A
 - class ConcreteObserverA implements Observer
 - {
 - public void process()
 - {
 - System.out.println("具體觀察者A處理!");
 - }
 - }
 - //具體觀察者B
 - class ConcreteObserverB implements Observer
 - {
 - public void process()
 - {
 - System.out.println("具體觀察者B處理!");
 - }
 - }
 
【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】





























 
 
 





 
 
 
 