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