如何理解這6種常見設計模式?

来源:https://www.cnblogs.com/88223100/archive/2023/01/20/How-to-understand-these-six-common-design-patterns.html
-Advertisement-
Play Games

導讀:設計模式能夠幫助我們優化代碼結構,讓代碼更優雅靈活。有哪些常見的設計模式?如何合理運用?本文分享作者對工廠模式、單例模式、裝飾模式、策略模式、代理模式和觀察者模式的理解,介紹每種模式的模式結構、優缺點、適用場景、註意實現及代碼實現。 ...


 

導讀:設計模式能夠幫助我們優化代碼結構,讓代碼更優雅靈活。有哪些常見的設計模式?如何合理運用?本文分享作者對工廠模式、單例模式、裝飾模式、策略模式、代理模式和觀察者模式的理解,介紹每種模式的模式結構、優缺點、適用場景、註意實現及代碼實現。

一  前言
最近在改造一些歷史的代碼,發現一個很明顯的特點,大部分代碼是記敘文,按照事件的發展過程將故事平鋪直敘的講解出來。
這種方式的好處是比較符合人類的思維習慣,一條主線講到底,代碼閱讀起來沒有太大難度,只要順著藤就能摸到瓜,但是缺點也很明顯,一旦故事線中需要插入一些新的元素,比如:加入一個新的人物角色、新的時間線,都會需要大量更改故事線以配合這個新元素的融入,甚至對原有文章造成破壞性的影響。
為瞭解決這個問題,人們總結出了很多種文章結構,例如:總-分結構,併列結構,總-分-總結構等等,有了這些結構,在加入新元素的時候,甚至不必考慮新元素與原故事情節的關聯性,直接單拉一個分支故事線獨立去講就好了,只要能夠在整體故事結束前,與匯聚到主線故事就可以了(是不是很像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)簡單實現
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();
    }
}

 

2  單例模式(Singleton)
韋小寶有7個老婆,但是每個都只有他這一個老公,他的所有老婆叫老公時,指的都是他,他就是一個單例。
單例模式結構
單例模式包含如下角色:
  • Singleton:單例

結構圖:
圖片
時序圖:
圖片
優缺點
  • 優點:全局只有一個實例,便於統一控制,同時減少了系統資源開銷。

  • 缺點:沒有抽象層,擴展困難。

應用場景
適合需要做全局統一控制的場景,例如:全局唯一的編碼生成器。
註意事項
只對外提供公共的getInstance方法,不提供任何公共構造函數。
簡單實現

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)
大學畢業,想要送給室友一個有紀念意義的禮物,就找到一張大家的合照,在上面寫上“永遠的兄弟!”,然後拿去禮品店裝了個相框,再包上禮盒。這裡的我和禮品店都是裝飾器,都沒有改變照片本身,卻都讓照片變得更適合作為禮物送人。
裝飾模式結構
裝飾模式包含如下角色:
  • Component:抽象構件

  • ConcreteComponent:具體構件

  • Decorator:抽象裝飾類

  • ConcreteDecorator:具體裝飾類

結構圖:
時序圖:
優缺點
  • 優點:比繼承更加靈活(繼承是耦合度很大的靜態關係),可以動態的為對象增加職責,可以通過使用不同的裝飾器組合為對象擴展N個新功能,而不會影響到對象本身。

  • 缺點:當一個對象的裝飾器過多時,會產生很多的裝飾類小對象和裝飾組合策略,增加系統複雜度,增加代碼的閱讀理解成本。

適用場景
  • 適合需要(通過配置,如:diamond)來動態增減對象功能的場景。

  • 適合一個對象需要N種功能排列組合的場景(如果用繼承,會使子類數量爆炸式增長)

註意事項
  • 一個裝飾類的介面必須與被裝飾類的介面保持相同,對於客戶端來說無論是裝飾之前的對象還是裝飾之後的對象都可以一致對待。

  • 儘量保持具體構件類Component作為一個“輕”類,也就是說不要把太多的邏輯和狀態放在具體構件類中,可以通過裝飾類。

簡單實現
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()");           
    }
}

 

4  策略模式(Strategy)
男生追妹子時,一般都會用到這種模式,常見的策略有這些:約會吃飯;看電影;看演唱會;逛街;去旅行……,雖然做的事情不同,但可以相互替換,唯一的目標都是捕獲妹子的芳心。
策略模式結構
  • Context: 環境類

  • Strategy: 抽象策略類

  • ConcreteStrategy: 具體策略類

結構圖:
時序圖:
圖片
優缺點
  • 優點:策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇演算法或行為。幹掉複雜難看的if-else。

  • 缺點:調用時,必須提前知道都有哪些策略模式類,才能自行決定當前場景該使用何種策略。

試用場景
一個系統需要動態地在幾種可替換演算法中選擇一種。不希望使用者關心演算法細節,將具體演算法封裝進策略類中。
註意事項
一定要在策略類的註釋中說明該策略的用途和適用場景。
簡單實現
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的策略方法被訪問!");
  }
}
//環境類
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)
淘寶店客服總是會收到非常多的重覆問題,例如:有沒有現貨?什麼時候發貨?發什麼快遞?大量回答重覆性的問題太煩了,於是就出現了小蜜機器人,他來幫客服回答那些已知的問題,當碰到小蜜無法解答的問題時,才會轉到人工客服。這裡的小蜜機器人就是客服的代理。
代理模式結構
代理模式包含如下角色:
  • Subject: 抽象主題角色

  • Proxy: 代理主題角色

  • RealSubject: 真實主題角色

結構圖:
圖片
時序圖:
優缺點
  • 優點:代理可以協調調用方與被調用方,降低了系統的耦合度。根據代理類型和場景的不同,可以起到控制安全性、減小系統開銷等作用。

  • 缺點:增加了一層代理處理,增加了系統的複雜度,同時可能會降低系統的相應速度。

試用場景
理論上可以代理任何對象,常見的代理模式有:
  • 遠程(Remote)代理:為一個位於不同的地址空間的對象提供一個本地的代理對象,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中,遠程代理又叫做大使(Ambassador)。

  • 虛擬(Virtual)代理:如果需要創建一個資源消耗較大的對象,先創建一個消耗相對較小的對象來表示,真實對象只在需要時才會被真正創建。

  • Copy-on-Write代理:它是虛擬代理的一種,把複製(克隆)操作延遲到只有在客戶端真正需要時才執行。一般來說,對象的深克隆是一個開銷較大的操作,Copy-on-Write代理可以讓這個操作延遲,只有對象被用到的時候才被克隆。

  • 保護(Protect or Access)代理:控制對一個對象的訪問,可以給不同的用戶提供不同級別的使用許可權。

  • 緩衝(Cache)代理:為某一個目標操作的結果提供臨時的存儲空間,以便多個客戶端可以共用這些結果。

  • 防火牆(Firewall)代理:保護目標不讓惡意用戶接近。

  • 同步化(Synchronization)代理:使幾個用戶能夠同時使用一個對象而沒有衝突。

  • 智能引用(Smart Reference)代理:當一個對象被引用時,提供一些額外的操作,如將此對象被調用的次數記錄下來等。

簡單實現

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("訪問真實主題之後的後續處理。");
    }
}

 

6  觀察者模式(Observer)
出差在外,想瞭解孩子在家的情況,這時候只要加入“相親相愛一家人”群,老爸老媽會經常把孩子的照片和視頻發到群里,你要做的就是作為一個觀察者,刷一刷群里的信息就能夠瞭解一切了。
觀察者模式結構
觀察者模式包含如下角色:
  • Subject:目標

  • ConcreteSubject:具體目標

  • Observer:觀察者

  • ConcreteObserver:具體觀察者

結構圖:
圖片
時序圖:
圖片
優缺點

 

  • 優點:將複雜的串列處理邏輯變為單元化的獨立處理邏輯,被觀察者只是按照自己的邏輯發出消息,不用關心誰來消費消息,每個觀察者只處理自己關心的內容。邏輯相互隔離帶來簡單清爽的代碼結構。

  • 缺點:觀察者較多時,可能會花費一定的開銷來發消息,但這個消息可能僅一個觀察者消費。

適用場景
適用於一對多的的業務場景,一個對象發生變更,會觸發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;

        // 狀態改變通知觀察者
        notify();
    }
    public void notify()
    {
        System.out.println("具體目標狀態發生改變...");
        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處理!");
    }
}

 

作者: 銘劭

本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/How-to-understand-these-six-common-design-patterns.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這周收到兩片基於LGT8F328P LQFP32的Arduino Mini EVB, 機器上沒有 Arduino 環境需要新安裝, 正好感受一下新出的 Arduino IDE 2.x, 記錄一下 Ubuntu 20.04/22.04 下安裝 Arduino IDE 2.x 的過程. ...
  • 最近幾天用Python寫了個介面後臺服務,把它部在了伺服器的300埠上。之後,我又要把一個二級功能變數名稱解析到該伺服器的300埠上,此時就要用到NGINX了。因為之前對NGINX的使用並不熟悉,特此記錄下來,方便回顧。用了該方法,就能為伺服器上的多個不同介面服務綁定不同的功能變數名稱了,這也就是把多個功能變數名稱解析 ...
  • 打算整理彙編語言與介面微機這方面的學習記錄。 參考資料 西電《微機原理與系統設計》周佳社 西交《微機原理與介面技術》 課本《彙編語言與介面技術》王讓定 小甲魚《彙編語言》 1. 介紹 2022年春學習了MIPS視角下的電腦體繫結構與組成原理,同年夏自學了操作系統(科普級別的瞭解),彙編語言是學習操 ...
  • 使用子查詢 子查詢 查詢(query) 任何SQL語句都是查詢。但此術語一般指SELECT語句。 SQL還允許創建子查詢(subquery),即嵌套在其他查詢中的查詢。 利用子查詢進行過濾 SELECT cust_id FROM orders WHERE order_num IN (SELECT o ...
  • 比較簡單,只是模擬彩票出數字的過程,不計算單一數字的出現概率。 傳統上來說,每次彩票出號的概率都是獨立事件,單純的在可選數字內隨機實現即可。 本文探索的是實現簡單的預測分析,包含歷史開獎結果的連續事件。 舉例說明:(模擬三個數字,數字區域1-10) 第一次開獎 1,2,3 第二次預測 [4-10] ...
  • 前言 在公司年會期間我做了個抽獎小項目,我把它分享出來,有用得著的可以看下。 瀏覽鏈接:http://xisite.top/original/luck-draw/index.html 項目鏈接:https://gitee.com/xi1213/luck-draw (歡迎star!) 項目截圖: 實現 ...
  • 1、使用 Array.prototype.some() 方法代替 some() 方法會在找到第一個符合條件的元素時停止迴圈。 例如: let array = [1, 2, 3, 4, 5]; array.some(function(element, index, array) { if (eleme ...
  • 本文我們來探討下如何引入分散式鎖解決本地鎖的問題。本篇所有代碼和業務基於我的開源項目 PassJava。 本篇主要內容如下: 一、本地鎖的問題 首先我們來回顧下本地鎖的問題: 目前題目微服務被拆分成了四個微服務。前端請求進來時,會被轉發到不同的微服務。假如前端接收了 10 W 個請求,每個微服務接收 ...
一周排行
    -Advertisement-
    Play Games
  • 一:背景 1. 講故事 年前遇到了好幾例托管堆被損壞的案例,有些運氣好一些,從被破壞的托管堆記憶體現場能觀測出大概是什麼問題,但更多的情況下是無法做出準確判斷的,原因就在於生成的dump是第二現場,借用之前文章的一張圖,大家可以理解一下。 為了幫助更多受此問題困擾的朋友,這篇來整理一下如何 快狠準 的 ...
  • 前言 .NET6 開始,.NET Croe API 項目取消了 Startup.cs 文件,在 Program.cs 文件的 Main 函數中完成服務的註冊和中間件管道的管理。但當我們項目引入更多包的時候,Program.cs 文件也會看起來很臃腫。 而且,我們不只會有一個後端項目,為了方便快速創建 ...
  • 目錄 背景 get 與 post 的區別 所有介面都用 post 請求? 背景 最近在逛知乎的時候發現一個有趣的問題:公司規定所有介面都用 post 請求,這是為什麼? 看到這個問題的時候其實我也挺有感觸的,因為我也曾經這樣問過我自己。在上上一家公司的時候接到一個項目是從零開始搭建一個微服務,當時就 ...
  • *以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://mp.weixin.qq.com/s/2GFLTstDC7w6u3fTJxflNA 本文大概 1685 個字,閱讀需花 6 分鐘內容不多, 但也花了一些精力如要交流, 歡迎關註我然後評論區留言 謝謝你的 ...
  • 在新版本的pandas中,上述代碼會引起警告,建議改成SQLAlchemy connectable(engine/connection),後續代碼將引入這種升級的連接方式。 ...
  • 幾乎所有的高級編程語言都有自己的垃圾回收機制,開發者不需要關註記憶體的申請與釋放,Python 也不例外。Python 官方團隊的文章 https://devguide.python.org/internals/garbage-collector 詳細介紹了 Python 中的垃圾回收演算法,本文是這篇 ...
  • 如果您想查找高於或低於平均值的數字,可以不必計算該平均值,就能查看更高或更低的值。通過Java應用程式,可以自動突出顯示這些數字。除了快速突出顯示高於或低於平均值的值外,您還可以查看高於或低於的值的個數。現在讓我們看看如何在 Java應用程式中實現此操作。 引入jar包 導入方法1: 手動引入。將  ...
  • 第一種方式:使用{} firstDict = {"name": "wang yuan wai ", "age" : 25} 說明:{}為創建一個空的字典對象 第二種方式:使用fromkeys()方法 second_dict = dict.fromkeys(("name", "age")) #valu ...
  • 在golang中可以使用a := b這種方式將b賦值給a,只有當b能進行深拷貝時a與b才不會互相影響,否則就需要進行更為複雜的深拷貝。 下麵就是Go賦值操作的一個說明: Go語言中所有賦值操作都是值傳遞,如果結構中不含指針,則直接賦值就是深度拷貝;如果結構中含有指針(包括自定義指針,以及切片,map ...
  • 本文結合京東監控埋點場景,對解決樣板代碼的技術選型方案進行分析,給出最終解決方案後,結合理論和實踐進一步展開。通過關註文中的技術分析過程和技術場景,讀者可收穫一種樣板代碼思想過程和解決思路,並對Java編譯器底層有初步瞭解。 ...