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