一文總結Java的23種設計模式

互聯網高級架構師 發佈 2023-03-18T21:26:13.334542+00:00

Java的23種設計模式 Java中的23種設計模式主要分為三類:創建型模式:主要解決對象創建問題結構型模式:主要解決對象組合問題行為型模式:主要解決對象之間的交互問題創建型模式Java中的創建型設計模式主要用於對象的創建和組裝。

Java的23種設計模式

Java中的23種設計模式主要分為三類:

  • 創建型模式:主要解決對象創建問題
  • 結構型模式:主要解決對象組合問題
  • 行為型模式:主要解決對象之間的交互問題

創建型模式

Java中的創建型設計模式主要用於對象的創建和組裝。這些模式通過抽象化和解耦對象的創建過程,可以使系統更加靈活和可擴展。下面是Java中的5種創建型設計模式:

  • 單例模式:確保一個類只有一個實例,並提供一個全局訪問點。
  • 工廠模式:在不暴露創建對象的邏輯的前提下,使用工廠方法來創建對象。
  • 抽象工廠模式:提供一個接口,用於創建相關或依賴對象的系列,而不需要指定實際實現類。
  • 建造者模式:將複雜對象的構建與表示分離,使得同樣的構建過程可以創建不同的表示。
  • 原型模式: 通過克隆來創建對象,避免了通過new關鍵字顯式調用構造函數的開銷。

結構型模式

Java中的結構型設計模式主要用於描述對象之間的關係,包括類和對象的組合、接口和繼承等方面。這些模式可以幫助我們更好地組織和管理代碼,提高代碼的重用性和可維護性。

下面是Java中的7種結構型設計模式:

  • 適配器模式:將一個類的接口轉換成客戶希望的另一個接口,使得原本由於接口不兼容而無法一起工作的類可以一起工作。
  • 橋接模式:將抽象部分與它的實現部分分離,以便它們可以獨立地變化。
  • 組合模式:將對象組合成樹形結構以表示「部分-整體」的層次結構,使得客戶端使用單個對象或者組合對象具有一致性。
  • 裝飾器模式:動態地給一個對象添加一些額外的職責,就增加功能而言,裝飾器模式比生成子類方式更為靈活。
  • 外觀模式:為子系統中的一組接口提供一個一致的界面,使得子系統更容易使用。
  • 享元模式:運用共享技術來有效地支持大量細粒度對象的復用。
  • 代理模式:為其他對象提供一種代理以控制對這個對象的訪問。

行為型模式

Java中的行為型設計模式主要用於描述對象之間的通信和協作方式,包括算法、責任鏈、狀態等方面。這些模式可以幫助我們更好地組織和管理代碼,提高代碼的可維護性和可擴展性。

下面是Java中的11種行為型設計模式:

  • 責任鏈模式:為解除請求的發送者和接收者之間的耦合,而將請求的處理對象連成一條鏈,並沿著這條鏈傳遞請求,直到有一個對象處理它為止。
  • 命令模式:將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。
  • 解釋器模式:給定一個語言,定義它的文法的一種表示,並定義一個解釋器,使用該解釋器來解釋語言中的句子。
  • 疊代器模式:提供一種方法順序訪問一個聚合對象中各個元素,而又不需要暴露該對象的內部表示。
  • 中介者模式:用一個中介對象封裝一系列的對象交互,使得這些對象不需要顯示地相互引用,從而降低耦合度。
  • 備忘錄模式:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。
  • 觀察者模式:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
  • 狀態模式:允許一個對象在其內部狀態發生改變時改變其行為,對象看起來似乎修改了它的類。
  • 策略模式:定義一系列的算法,將每個算法封裝起來,並使它們之間可以互換。
  • 模板方法模式:定義一個操作中的算法骨架,將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下重新定義算法中的某些步驟。
  • 過濾器設計模式:允許在不改變原始對象的情況下,動態地添加或刪除對象的行為。

設計原則與設計模式

在了解完設計模式之後,我們再來了解下:六大設計原則

  1. 單一職責原則:一個類應該只有一個引起它變化的原因。換句話說,一個類應該只有一項職責。這樣可以保證類的內聚性,並且降低類之間的耦合性。
  2. 開閉原則:一個軟體實體如類、模塊和函數應該對擴展開放,對修改關閉。這意味著當需要添加新功能時,應該儘量通過擴展已有代碼來實現,而不是修改已有代碼。
  3. 里氏替換原則:子類應該能夠替換父類並且不影響程序的正確性。這意味著在使用繼承時,子類不能修改父類已有的行為,而只能擴展父類的功能。
  4. 接口隔離原則:客戶端不應該依賴於它不需要的接口。一個類應該只提供它需要的接口,而不應該強迫客戶端依賴於它不需要的接口。
  5. 依賴倒置原則:高層模塊不應該依賴於低層模塊,它們都應該依賴於抽象。抽象不應該依賴於具體實現,而具體實現應該依賴於抽象。
  6. 迪米特法則:一個對象應該對其他對象保持最少的了解。換句話說,一個對象只應該與它直接相互作用的對象發生交互,而不應該與其它任何對象發生直接的交互。這樣可以降低類之間的耦合性,提高系統的靈活性和可維護性。

設計模式與設計原則他們有什麼不同呢?

設計原則和設計模式是面向對象設計中的兩個重要概念,它們相互關聯,但又有不同的含義和作用:

  • 設計原則是一些通用的設計指導方針,它們提供了如何設計一個優秀的軟體系統的基本思想和規則。指導著設計者如何組織代碼以實現高內聚、低耦合、易擴展和易維護的軟體系統。
  • 設計模式則是在特定情況下解決常見問題的經驗性解決方案,它們提供了如何實現這些設計原則的具體方法。

設計模式往往是在滿足設計原則的基礎上被應用的。設計模式可以看作是實現設計原則的一種具體方式。

組合使用

在實際開發中,我們很少只使用單一的設計模式來解決問題,而是將多種設計模式混合使用,以達到更好的效果。

工廠模式 + 單例模式

使用工廠模式來創建對象,通過單例模式來保證該工廠只有一個實例,從而減少創建對象時的開銷。

首先,創建一個工廠類,該類使用單例模式來保證只有一個實例,該實例負責創建對象。然後,根據需要創建多個工廠方法,每個方法用於創建不同的對象。

public class SingletonFactory {
    private static SingletonFactory instance;

    private SingletonFactory() {
        // 私有構造方法
    }

    public static SingletonFactory getInstance() {
        if (instance == null) {
            synchronized (SingletonFactory.class) {
                if (instance == null) {
                    instance = new SingletonFactory();
                }
            }
        }
        return instance;
    }

    public Object createObject(String type) {
        if ("type1".equals(type)) {
            return new Type1();
        } else if ("type2".equals(type)) {
            return new Type2();
        } else {
            throw new IllegalArgumentException("Unsupported type: " + type);
        }
    }
}

public class Type1 {
    // 類型1實現邏輯
}

public class Type2 {
    // 類型2實現邏輯
}

SingletonFactory 類使用雙重檢查鎖定實現了單例模式,同時提供了一個 createObject() 方法,該方法根據輸入的參數來創建不同的對象。Type1 和 Type2 類分別代表了不同類型的對象,它們包含了各自的實現邏輯。

模板方法模式 + 策略模式

使用模板方法模式來定義算法的骨架,同時使用策略模式來定義算法的不同實現方式,以實現更高的靈活性。

假設我們要實現一個圖片處理程序,可以對不同類型的圖片進行處理,包括縮放、旋轉和裁剪等操作。具體的處理算法可以根據不同類型的圖片而異。

首先,我們定義一個抽象類 ImageProcessor,它包含一個模板方法 processImage(),該方法定義了一系列的處理步驟,包括打開圖片、執行具體的處理算法和保存圖片等。其中,具體的處理算法是由策略模式來實現的,我們使用一個抽象策略接口 ImageProcessingStrategy 來定義不同的處理算法。

public abstract class ImageProcessor {

    public void processImage() {
        BufferedImage image = openImage();
        ImageProcessingStrategy strategy = createImageProcessingStrategy();
        BufferedImage processedImage = strategy.processImage(image);
        saveImage(processedImage);
    }

    protected BufferedImage openImage() {
        // 打開圖片的具體實現
    }

    protected abstract ImageProcessingStrategy createImageProcessingStrategy();

    protected void saveImage(BufferedImage image) {
        // 保存圖片的具體實現
    }
}

public interface ImageProcessingStrategy {

    BufferedImage processImage(BufferedImage image);
}

然後,定義具體的圖片處理類 JpegProcessor 和 PngProcessor,它們分別繼承自 ImageProcessor,並實現 createImageProcessingStrategy() 方法,返回不同的處理算法策略。

public class JpegProcessor extends ImageProcessor {

    @Override
    protected ImageProcessingStrategy createImageProcessingStrategy() {
        return new JpegProcessingStrategy();
    }
}

public class PngProcessor extends ImageProcessor {

    @Override
    protected ImageProcessingStrategy createImageProcessingStrategy() {
        return new PngProcessingStrategy();
    }
}

最後,定義不同的處理算法策略,例如 JpegProcessingStrategy 和 PngProcessingStrategy,它們實現了 ImageProcessingStrategy 接口,提供了具體的處理算法實現。

public class JpegProcessingStrategy implements ImageProcessingStrategy {

    @Override
    public BufferedImage processImage(BufferedImage image) {
        // Jpeg 圖片處理算法
    }
}

public class PngProcessingStrategy implements ImageProcessingStrategy {

    @Override
    public BufferedImage processImage(BufferedImage image) {
        // Png 圖片處理算法
    }
}

這樣,實現一個可擴展和可定製的圖片處理程序。在運行時,我們可以根據不同的圖片類型選擇不同的處理算法,從而實現不同的圖片處理效果。

策略模式 + 工廠模式

使用工廠模式來創建不同的策略對象,然後使用策略模式來選擇不同的策略,以實現不同的功能。我們實現一個簡單的計算器。

首先,我們定義一個 CalculatorStrategy 接口,其中包含了兩個方法:calculate 用於計算兩個數的結果,getDescription 用於獲取當前策略的描述信息。

public interface CalculatorStrategy {
    double calculate(double num1, double num2);
    String getDescription();
}

然後,我們實現幾個具體的計算策略類,如加法、減法、乘法和除法:

public class AddStrategy implements CalculatorStrategy {
    @Override
    public double calculate(double num1, double num2) {
        return num1 + num2;
    }

    @Override
    public String getDescription() {
        return "加法";
    }
}

public class SubtractStrategy implements CalculatorStrategy {
    @Override
    public double calculate(double num1, double num2) {
        return num1 - num2;
    }

    @Override
    public String getDescription() {
        return "減法";
    }
}

public class MultiplyStrategy implements CalculatorStrategy {
    @Override
    public double calculate(double num1, double num2) {
        return num1 * num2;
    }

    @Override
    public String getDescription() {
        return "乘法";
    }
}

public class DivideStrategy implements CalculatorStrategy {
    @Override
    public double calculate(double num1, double num2) {
        return num1 / num2;
    }

    @Override
    public String getDescription() {
        return "除法";
    }
}

接下來,我們使用工廠模式來創建具體的策略對象。我們創建一個 CalculatorStrategyFactory 工廠類,其中定義了一個 getCalculatorStrategy 方法,根據傳入的操作符,返回相應的計算策略對象。

public class CalculatorStrategyFactory {
    public static CalculatorStrategy getCalculatorStrategy(String operator) {
        switch (operator) {
            case "+":
                return new AddStrategy();
            case "-":
                return new SubtractStrategy();
            case "*":
                return new MultiplyStrategy();
            case "/":
                return new DivideStrategy();
            default:
                throw new IllegalArgumentException("無效的操作符:" + operator);
        }
    }
}

我們創建一個 Calculator 類,其中包含一個 calculate 方法,根據傳入的兩個數和操作符,返回計算結果。

public class Calculator {
    public static double calculate(double num1, double num2, String operator) {
        CalculatorStrategy calculatorStrategy = CalculatorStrategyFactory.getCalculatorStrategy(operator);
        System.out.println("正在執行 " + calculatorStrategy.getDescription() + " 計算");
        return calculatorStrategy.calculate(num1, num2);
    }
}

//調用計算器
// 執行加法計算
double result = Calculator.calculate(10, 5, "+"); 
 // 輸出 15.0
System.out.println(result);

適配器模式 + 裝飾器模式

適配器模式用於將一個接口轉換成另一個接口,而裝飾器模式則用於動態地給對象添加一些額外的職責。當我們需要將一個已有的接口轉換成新的接口,並且還需要給對象添加一些額外的職責時,可以使用這兩個模式混合使用。

首先定義一個需要被適配的接口:

public interface MediaPlayer {
   public void play(String audioType, String fileName);
}

然後定義一個可以播放 mp3 文件的具體類:

public class Mp3Player implements MediaPlayer {

   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: " + fileName);         
      } 
   }
}

接下來定義一個適配器,將 AdvancedMediaPlayer 接口轉換成 MediaPlayer 接口

public class MediaPlayerAdapter implements MediaPlayer {

   AdvancedMediaPlayer advancedMusicPlayer;

   public MediaPlayerAdapter(AdvancedMediaPlayer advancedMusicPlayer){
      this.advancedMusicPlayer = advancedMusicPlayer;
   }

   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.play(audioType, fileName);
      }
      else if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: " + fileName);
      }
   }
}

然後定義一個裝飾器類,用於在播放 mp3 文件時,動態地添加一些額外的功能:

public class Mp3PlayerDecorator implements MediaPlayer {

   private MediaPlayer mediaPlayer;

   public Mp3PlayerDecorator(MediaPlayer mediaPlayer) {
      this.mediaPlayer = mediaPlayer;
   }

   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file with additional features. Name: " + fileName);
         // 添加額外的功能
         System.out.println("Adding equalizer to the mp3 file.");
         mediaPlayer.play(audioType, fileName);
      }
      else {
         mediaPlayer.play(audioType, fileName);
      }
   }
}

最後,我們可以使用適配器和裝飾器來播放不同類型的音頻文件:

public static void main(String[] args) {
   MediaPlayer mediaPlayer = new Mp3Player();
   mediaPlayer.play("mp3", "song.mp3");

   AdvancedMediaPlayer advancedMediaPlayer = new VlcPlayer();
   MediaPlayer mediaPlayerAdapter = new MediaPlayerAdapter(advancedMediaPlayer);
   mediaPlayerAdapter.play("vlc", "movie.vlc");

   MediaPlayer decoratedMediaPlayer = new Mp3PlayerDecorator(mediaPlayer);
   decoratedMediaPlayer.play("mp3", "song.mp3");
}

輸出結果

Playing mp3 file. Name: song.mp3
Playing vlc file. Name: movie.vlc
Playing mp3 file with additional features. Name: song.mp3
Adding equalizer to the mp3 file.
Playing mp3 file. Name: song.mp3

觀察者模式 + 命令模式

觀察者模式用於觀察對象的狀態變化,並及時通知觀察者。而命令模式則用於將一個請求封裝成一個對象,可以在運行時動態地切換命令的接收者。當我們需要觀察對象的狀態變化,並在狀態變化時執行一些命令時,可以使用這兩個模式混合使用。

首先,我們創建一個接口 Observer 來表示觀察者對象,其中包含一個 update() 方法用於更新觀察者狀態。

public interface Observer {
    void update();
}

接著,我們創建一個類 Subject 來表示被觀察者對象,其中包含一些觀察者對象的引用和一些方法用於註冊、註銷和通知觀察者。

import java.util.ArrayList;
import java.util.List;

public class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void register(Observer observer) {
        observers.add(observer);
    }

    public void unregister(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

接下來,我們創建一個接口 Command 來表示命令對象,其中包含一個 execute() 方法用於執行命令。

public interface Command {
    void execute();
}

然後,我們創建一個具體命令類 ConcreteCommand,該類實現了 Command 接口,其中包含一個 Subject 對象的引用和一個 execute() 方法,該方法會調用 Subject 對象的 notifyObservers() 方法通知觀察者對象。

public class ConcreteCommand implements Command {
    private Subject subject;

    public ConcreteCommand(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void execute() {
        subject.notifyObservers();
    }
}

最後,我們創建一個具體觀察者類 ConcreteObserver,該類實現了 Observer 接口,其中包含一個 execute() 方法,該方法會輸出一條消息。

public class ConcreteObserver implements Observer {
    @Override
    public void update() {
        System.out.println("ConcreteObserver received notification.");
    }
}

現在,我們可以使用以下代碼將觀察者模式和命令模式混合使用:

public class Client {
    public static void main(String[] args) {
        // 創建一個被觀察者對象
        Subject subject = new Subject();

        // 創建一個觀察者對象
        Observer observer = new ConcreteObserver();

        // 註冊觀察者對象
        subject.register(observer);

        // 創建一個命令對象
        Command command = new ConcreteCommand(subject);

        // 執行命令,通知觀察者對象
        command.execute();
    }
}
//輸出  ConcreteObserver received notification.

小結

需要注意的是,混合使用設計模式要謹慎,不要過度使用設計模式,以免代碼過於複雜和難以維護。選擇合適的設計模式和合適的組合方式,可以使代碼更加簡潔、高效和易於維護。

作者:也許明天y
連結:https://juejin.cn/post/7211026540129157180
來源:稀土掘金

關鍵字: