Java常用設計模式(一)

語言: CN / TW / HK

  當代軟體開發中,設計模式已經成為一種標準的程式設計實踐。在Java程式設計中,設計模式也同樣重要。Java設計模式是軟體開發中廣泛應用的一種程式設計方法,它可以幫助開發人員更快地編寫出高效、可靠和可維護的程式碼。本文將介紹Java中常用的幾種設計模式,並解釋它們的優點和用途。

Java常用設計模式(二) - 掘金 (juejin.cn)

單例模式(Singleton Pattern)

  單例模式是最常用的設計模式之一。它可以確保在整個應用程式中,某個類只有一個例項存在,並提供一種訪問這個例項的全域性訪問點。單例模式在需要限制某些類的例項數量時非常有用。 它通常用於需要全域性訪問的資源,如配置檔案、日誌記錄器、資料庫連線等。

應用場景

  1. 日誌記錄器 在一個應用程式中,通常會有多個模組或類需要記錄日誌。為了避免建立多個日誌記錄器例項,使用單例模式可以確保只有一個日誌記錄器例項,從而避免重複記錄日誌並提高應用程式的效能。
  2. 資料庫連線 在一個應用程式中,如果需要頻繁地與資料庫互動,使用單例模式可以確保只有一個數據庫連線例項,從而減少資料庫連線的數量,提高應用程式的效能。
  3. 系統配置 在一個應用程式中,通常會有一些全域性的配置引數,如資料庫連線字串、伺服器地址、快取大小等。使用單例模式可以確保只有一個配置例項,從而方便管理和修改配置引數。

程式碼實現

懶漢式

``` public class Singleton { private static Singleton instance;

private Singleton() {
    // 私有建構函式,防止外部例項化
}

public static Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

}

```

靜態內部類方式

  SingletonHolder是一個靜態內部類,它包含一個靜態的INSTANCE成員變數,用於儲存單例物件。在第一次呼叫getInstance方法時,靜態內部類會被載入,從而建立單例物件。這種方式既兼顧了執行緒安全又兼顧了延遲載入的需求。 ``` public class Singleton { private Singleton() { // 私有建構函式,防止外部例項化 }

public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
}

}

```

餓漢式

  餓漢式在類載入時就建立了單例物件,所以不存線上程安全問題。不過,這種方式可能會導致不必要的資源浪費,因為單例物件的建立可能在應用程式啟動時就完成了,而有些應用場景中可能並不需要使用單例物件。

``` public class Singleton { // 在類載入時就建立單例物件 private static Singleton instance = new Singleton();

// 將建構函式設為私有,禁止外部建立例項
private Singleton() {}

// 提供獲取單例物件的方法
public static Singleton getInstance() {
    return instance;
}

}

```

雙重檢查鎖

  它可以在保證執行緒安全的同時實現延遲載入 ``` public class Singleton { private static volatile Singleton instance;

private Singleton() {}

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

}

```

列舉方式

  使用列舉實現單例模式的好處是,可以避免反射和序列化攻擊。因為列舉型別的建構函式是私有的,所以無法使用反射來建立例項;而且列舉型別的例項在序列化和反序列化時會自動處理好,所以也無法通過序列化和反序列化來破壞單例。

``` public enum Singleton { INSTANCE;

public void doSomething() {
    // TODO: 實現單例物件的功能
}

}

```

使用小結

  • 對執行緒安全和效能要求較高,可以考慮使用餓漢式雙重檢查鎖方式實現單例模式。這兩種方式都能保證執行緒安全,而且在大多數情況下效能也比較好。

  • 如果你對執行緒安全要求不是很高,或者希望在第一次訪問時才建立單例物件,可以考慮使用懶漢式或者靜態內部類方式。這兩種方式都是延遲載入的,只有在需要時才會建立單例物件。懶漢式不是執行緒安全的,需要通過加鎖等方式來保證執行緒安全;而靜態內部類方式則是天生執行緒安全的,不需要額外的處理。

  • 希望實現簡單、程式碼少,且不需要考慮執行緒安全和延遲載入的問題,可以考慮使用列舉方式。這種方式不僅程式碼簡單,而且天生執行緒安全、單例物件建立和呼叫都很方便。

  總之,選擇哪種實現方式需要根據具體需求來決定,需要綜合考慮執行緒安全、效能、程式碼複雜度、延遲載入等因素。

工廠模式(Factory Pattern)

  工廠模式是一種建立型模式,它可以為開發人員提供一種在不直接例項化物件的情況下建立物件的方法。工廠模式通過提供一個通用的介面和一組實現,來隱藏具體實現的細節,從而降低了程式碼的耦合度和依賴性。

應用場景

  1. 物件的建立過程比較複雜,需要進行封裝:如果建立一個物件需要進行復雜的初始化過程,或者需要從多個地方獲取資料才能建立物件,那麼使用工廠模式可以將這些過程封裝起來,讓客戶端程式碼更加簡潔和易於理解。

  2. 需要動態擴充套件或修改物件的建立過程:如果需要增加或修改某個物件的建立過程,而又不希望對客戶端程式碼產生影響,那麼使用工廠模式可以很方便地實現這個需求。

  3. 需要統一管理物件的建立:如果需要統一管理物件的建立過程,或者需要對建立的物件進行某些統一的處理,那麼使用工廠模式可以很好地實現這個需求。

  4. 需要根據不同的條件建立不同的物件:如果需要根據不同的條件來建立不同型別的物件,那麼使用工廠模式可以很方便地實現這個需求。

程式碼實現

通過一個工廠類來封裝物件的建立過程,客戶端只需要告訴工廠類需要建立哪種型別的物件即可。將物件的建立過程與客戶端程式碼分離開來,使程式碼更加靈活和易於擴充套件 ``` // 定義產品介面 public interface Product { void operation(); }

// 具體產品類A public class ConcreteProductA implements Product { @Override public void operation() { System.out.println("ConcreteProductA operation."); } }

// 具體產品類B public class ConcreteProductB implements Product { @Override public void operation() { System.out.println("ConcreteProductB operation."); } }

// 工廠類 public class SimpleFactory { public static Product createProduct(String type) { if ("A".equals(type)) { return new ConcreteProductA(); } else if ("B".equals(type)) { return new ConcreteProductB(); } else { throw new IllegalArgumentException("Invalid product type."); } } }

``   客戶端可以通過呼叫SimpleFactory.createProduct`方法來建立不同型別的產品物件

``` Product productA = SimpleFactory.createProduct("A"); productA.operation(); // 輸出 "ConcreteProductA operation."

Product productB = SimpleFactory.createProduct("B"); productB.operation(); // 輸出 "ConcreteProductB operation."

```

使用小結

  在Java中,工廠模式廣泛應用於各種框架和類庫中,例如JDBC中的DataSource工廠、Spring框架中的Bean工廠、MyBatis框架中的SqlSessionFactory等等。

觀察者模式(Observer Pattern)

  觀察者模式是一種行為型模式,它定義了物件之間的一種一對多的依賴關係。在這種模式中,一個物件發生變化時,所有依賴於它的物件都會得到通知並自動更新。觀察者模式可以幫助開發人員建立可擴充套件的應用程式,減少物件之間的直接依賴關係。

應用場景

  1. 事件處理機制:Java中的Swing GUI框架就是基於觀察者模式實現的,當用戶與元件互動時,元件會向註冊的監聽器傳送事件通知,以觸發相應的事件處理方法。

  2. 日誌記錄:Java中的日誌系統也是基於觀察者模式實現的,當日志發生變化時,它會通知所有註冊的觀察者,例如檔案輸出流、控制檯輸出流等,從而實現日誌的輸出和記錄。

  3. 使用者介面設計:在Java中,使用者介面設計中的許多元素都可以使用觀察者模式實現,例如選單項、按鈕、文字框等,當用戶與這些元素互動時,它們會向註冊的監聽器傳送事件通知,以觸發相應的事件處理方法。

  4. 多執行緒程式設計:在Java中,觀察者模式還可以用於多執行緒程式設計中,當一個執行緒發生了某些變化時,它可以向其他執行緒傳送通知,以實現執行緒間的協作和同步。

程式碼實現

  這個示例中,ConcreteSubject 實現了 Subject 介面,它維護了一個 observers 列表,用於儲存註冊的觀察者物件。當被觀察者發生變化時,它會遍歷觀察者列表,呼叫每個觀察者的 update 方法。

  ConcreteObserver 實現了 Observer 介面,它可以接收來自被觀察者的通知,並執行相應的操作。

  在測試類 ObserverPatternDemo 中,我們建立了一個具體的被觀察者物件 ConcreteSubject,並註冊了兩個具體的觀察者物件 observer1observer2。當被觀察者發生變化時,它會通知所有註冊的觀察者物件,並呼叫它們的 update 方法。

``` // 觀察者介面 interface Observer { void update(String message); }

// 被觀察者介面 interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(String message); }

// 具體的被觀察者實現類 class ConcreteSubject implements Subject { private List observers = new ArrayList<>();

@Override
public void registerObserver(Observer observer) {
    observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
    observers.remove(observer);
}

@Override
public void notifyObservers(String message) {
    for (Observer observer : observers) {
        observer.update(message);
    }
}

}

// 具體的觀察者實現類 class ConcreteObserver implements Observer { private String name;

public ConcreteObserver(String name) {
    this.name = name;
}

@Override
public void update(String message) {
    System.out.println(name + " received message: " + message);
}

}

// 測試類 public class ObserverPatternDemo { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Observer observer1 = new ConcreteObserver("Observer1"); Observer observer2 = new ConcreteObserver("Observer2"); subject.registerObserver(observer1); subject.registerObserver(observer2); subject.notifyObservers("Hello, World!"); } } ```

使用小結

  觀察者模式的優點在於它提供了一種鬆耦合的方式,讓觀察者和主題之間的依賴關係變得更加靈活,同時也可以使得程式更易於擴充套件和維護。

  觀察者模式的應用場景包括:當一個抽象模型有兩個方面,其中一個方面依賴於另一個方面時;當一個物件的改變需要同時改變其他物件的時候;當一個物件的改變需要通知其他物件而又不希望與被通知物件形成緊耦合關係時。

介面卡模式(Adapter Pattern)

  介面卡模式是一種結構型模式,它可以將一個類的介面轉換成客戶端所期望的另一種介面。介面卡模式可以幫助開發人員在不修改現有程式碼的情況下,將不相容的類組合在一起。介面卡模式包括以下幾個組成部分: - 目標介面(Target Interface):客戶端期望的介面。 - 介面卡(Adapter):充當兩個不相容介面之間的橋樑,使得它們可以互相通訊。 - 適配者(Adaptee):需要被適配的物件,它的介面與目標介面不相容。 - 客戶端(Client):使用目標介面的物件。

應用場景

  • 當需要將一個已有的類或介面與另一個不相容的類或介面進行協同工作時。

  • 當需要對一個已有的類或介面進行修改,以滿足客戶端的需求時,但是不希望修改該類或介面的原始碼。

  • 當需要重新使用一個已有的類或介面,但是不能直接使用該類或介面的方法時。

程式碼實現

  在這個示例中,我們有一個目標介面 Target 和一個不相容的適配者 Adaptee,我們需要建立一個介面卡 Adapter 來讓它們能夠一起工作。

  介面卡實現了目標介面 Target,並在建構函式中接受一個適配者物件 Adaptee,然後在實現目標介面的 request 方法中呼叫適配者的 specificRequest 方法。

  在客戶端中,我們建立了一個適配者物件 adaptee,並將其傳遞給介面卡的建構函式建立一個介面卡物件 adapter。最後,我們使用目標介面 Target 中定義的方法 request 來訪問介面卡,從而呼叫適配者的方法。 ``` // 目標介面 interface Target { void request(); }

// 適配者 class Adaptee { void specificRequest() { System.out.println("Adaptee specificRequest."); } }

// 介面卡 class Adapter implements Target { private Adaptee adaptee;

public Adapter(Adaptee adaptee) {
    this.adaptee = adaptee;
}

@Override
public void request() {
    adaptee.specificRequest();
}

}

// 客戶端 public class AdapterPatternDemo { public static void main(String[] args) { Adaptee adaptee = new Adaptee(); Target target = new Adapter(adaptee); target.request(); } }

```

使用小結

  介面卡模式是一種非常有用的設計模式,在 JDK 中被廣泛應用,可以提供一致的介面,比如 1. Java IO 流是一個常見的介面卡模式的例子。它提供了一組標準的介面來訪問各種型別的資料來源,包括檔案、網路連線、記憶體等等。每個資料來源都有自己的介面,但是 Java IO 流可以將這些不同的介面轉換為標準的介面,從而提供一致的訪問方式。 2. Java Servlet API 也是一個常見的介面卡模式的例子。它定義了一組介面來處理 HTTP 請求和響應,包括 doGet()、doPost()、doPut() 等等。每個 Servlet 都必須實現這些介面,但是使用者只需要實現其中的一部分即可。這些 Servlet 之間的適配工作由 Servlet 容器完成。

裝飾器模式(Decorator Pattern)

  裝飾器模式是一種結構型模式,它可以允許開發人員在不修改現有物件的情況下,動態地新增新功能。裝飾器模式通過將一個物件包裝在另一個物件中來擴充套件它的行為,從而提高了程式碼的靈活性和可重用性。

應用場景

  1. 當需要在不修改現有物件結構的前提下增加新的功能或特性時,可以使用裝飾器模式。這樣可以保持原有程式碼的穩定性和相容性,同時也可以增加程式碼的靈活性和可擴充套件性。

  2. 當需要動態地向物件新增或刪除功能時,可以使用裝飾器模式。這樣可以在執行時動態地新增或刪除功能,而不需要修改現有的程式碼。

  3. 當需要為多個物件新增相同的功能時,可以使用裝飾器模式。這樣可以將相同的功能封裝在裝飾器中,以便於複用和管理。

程式碼實現

  該示例程式碼中,Shape 是一個介面,定義了一個 draw 方法,表示繪製圖形的操作。Circle 是一個實現 Shape 介面的類,表示一個圓形。

  ShapeDecorator 是一個裝飾器抽象類,實現了 Shape 介面,幷包含一個 Shape 型別的變數 decoratedShape,表示要裝飾的物件。RedShapeDecorator 是一個具體的裝飾器類,繼承了 ShapeDecorator 類,並實現了 draw 方法,在繪製圖形時添加了一個紅色的邊框。

  在 main 方法中,我們建立了原始物件 Circle,以及兩個裝飾器物件 RedShapeDecorator,分別裝飾了 CircleRectangle 物件。通過呼叫 draw 方法,我們可以看到物件被動態地添加了一個紅色的邊框,而不需要修改原有的程式碼。

``` // 定義介面 interface Shape { void draw(); }

// 實現介面 class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } }

// 裝飾器抽象類 abstract class ShapeDecorator implements Shape { protected Shape decoratedShape;

public ShapeDecorator(Shape decoratedShape){
    this.decoratedShape = decoratedShape;
}

public void draw(){
    decoratedShape.draw();
}

}

// 具體裝飾器類 class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); }

@Override
public void draw() {
    decoratedShape.draw();
    setRedBorder(decoratedShape);
}

private void setRedBorder(Shape decoratedShape){
    System.out.println("Border Color: Red");
}

}

// 測試程式碼 public class DecoratorPatternDemo { public static void main(String[] args) { // 建立原始物件 Shape circle = new Circle();

    // 建立裝飾器物件
    Shape redCircle = new RedShapeDecorator(new Circle());
    Shape redRectangle = new RedShapeDecorator(new Rectangle());

    // 呼叫方法
    System.out.println("Circle with normal border");
    circle.draw();

    System.out.println("\nCircle of red border");
    redCircle.draw();

    System.out.println("\nRectangle of red border");
    redRectangle.draw();
}

}

```

使用小結

  在實際應用中,裝飾器模式經常用於圖形介面(GUI)開發、輸入/輸出流處理、快取機制、日誌記錄等領域,可以有效地提高程式的可擴充套件性和可維護性。比如

  1. 在 Java 中,裝飾器模式被廣泛應用於 Java IO 流中,以提供各種不同的功能,如快取、壓縮、加密等等。例如,可以使用 BufferedReader 來快取讀取檔案的資料,使用 GZIPOutputStream 來壓縮資料,使用 CipherOutputStream 來加密資料等等。

  2. Java Swing 元件是一個經典的裝飾器模式的例子。它允許在執行時動態地向元件新增功能,如邊框、背景、文字等等。例如,可以使用 BorderFactory 來向元件新增邊框,使用 Color 來設定元件的背景顏色,使用 Font 來設定元件的字型等等。

  3. 在 Spring 框架中,裝飾器模式被廣泛應用於實現 AOP。AOP通過代理模式和裝飾器模式實現。JDK 動態代理和 CGLIB 動態代理兩種方式實現代理模式,使用裝飾器模式對目標物件進行包裝,從而實現通知 (Advice) 的織入。例如,可以使用 @Transactional 來新增事務處理的功能,使用 @Cacheable 來新增快取處理的功能,等等。