java設計模式[3]之結構型模式

文章目錄

  • 一 代理模式
    • 1.1 靜態代理
      • 1.1.1 靜態代理的結構
      • 1.1.2 靜態代理的特點
      • 1.1.3 靜態代理的應用場景
      • 1.1.4 靜態代理的案例代碼
    • 1.2 JDK動態代理
      • 1.2.1 JDK動態代理概述
      • 1.2.2 JDK動態代理案例代碼
      • 1.2.3 JDK動態代理的應用場景
      • 1.2.4 JDK動態代理的特點
      • 1.2.5 與創建型模式的區別
    • 1.3 CGLIB動態代理
      • 1.3.1 CGLIB動態代理概述
      • 1.3.2 CHLIB動態代理代碼案例
    • 1.4 CGLIB vs JDK 動態代理對比
    • 1.5 代理模式的經典應用
    • 1.6 代理模式的經典應用
  • 二 裝飾器模式
    • 2.1 裝飾器模式的核心組成
    • 2.2 裝飾器模式的代碼案例
    • 2.3 裝飾器模式的優點
    • 2.4 裝飾器模式的適用場景
  • 三 適配器模式
    • 3.1 適配器模式的主要角色
    • 3.2 適配器模式的兩種實現方式
    • 3.3 適配器模式的代碼案例
      • 3.3.1 目標接口 `Target`
      • 3.3.2 被適配者 `Adaptee`
      • 3.3.3 對象適配器 `ObjectAdapter`
      • 3.3.4 類適配器 `ClassAdapter`(如果使用繼承)
      • 3.3.5 客戶端代碼
    • 3.4 適配器模式的應用場景
    • 3.5 適配器模式的優缺點
  • 四 組合模式
    • 4.1 組合模式的主要角色
    • 4.2 組合模式的UML 結構圖
    • 4.3 組合模式的代碼案例
      • 4.3.1 定義 `Component` 接口
      • 4.3.2 實現 `Leaf` 類
      • 4.3.3 實現 `Composite` 類
      • 4.3.4 客戶端使用
    • 4.4 組合模式的適用場景
    • 4.5 組合模式的優缺點
  • 五 門面模式
    • 5.1 門面模式的核心思想
    • 5.2 門面模式的主要角色
    • 5.3 門面模式的代碼案例
    • 5.4 門面模式的應用場景
    • 5.5 門面模式的優缺點
  • 六 橋接模式
    • 6.1 橋接模式的主要角色
    • 6.2 橋接模式的應用場景
    • 6.3 橋接模式的代碼案例
    • 6.4 橋接模式的優缺點
    • 6.5 橋接模式的經典應用
  • 七 享元模式
    • 7.1 享元模式的主要角色
    • 7.2享元模式的代碼實例
      • 7.2.1 抽象享元類
      • 7.2.2 具體享元類
      • 7.2.3 享元工廠類
      • 7.2.4 客戶端調用
    • 7.3享元模式的優缺點
    • 7.4 享元模式的適用場景
    • 7.5 享元模式的經典應用

在這里插入圖片描述

一 代理模式

在這里插入圖片描述

  • 代理模式的作用是通過代理對象來增強目標對象的功能。利用AOP橫切的思想。代理模式的實現方式有三種:靜態代理,動態代理(JDK動態代理和CGLIB動態代理)。

1.1 靜態代理

  • 靜態代理是 Java 中一種常見的設計模式,屬于創建型模式的一種。它主要用于在不修改目標對象的前提下,通過代理對象增強目標對象的功能。

1.1.1 靜態代理的結構

  • 接口(Subject):定義目標對象和代理對象的公共接口。
  • 目標類(RealSubject):實現接口,提供核心功能。
  • 代理類(Proxy):也實現接口,內部持有目標對象的引用,可以在調用目標對象的方法前后添加額外操作。

1.1.2 靜態代理的特點

  • 優點

    • 符合開閉原則:不需要修改目標對象,就可以為其增加新的功能。
    • 提高代碼解耦性:代理對象和目標對象職責清晰。
  • 缺點

    • 每個接口都需要一個代理類,會導致類數量增加。
    • 如果接口方法發生變化,代理類也需要同步修改。

1.1.3 靜態代理的應用場景

  • 日志記錄、性能監控。
  • 權限控制。
  • 遠程調用等需要對目標對象進行封裝的場景。

1.1.4 靜態代理的案例代碼

  1. 定義接口 Subject
public interface Subject {void request();
}
  1. 實現目標類 RealSubject
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("RealSubject: 處理請求");}
}
  1. 創建代理類 Proxy
public class Proxy implements Subject {private Subject realSubject;public Proxy(Subject realSubject) {this.realSubject = realSubject;}@Overridepublic void request() {System.out.println("Proxy: 請求前的預處理");realSubject.request(); // 調用真實對象的方法System.out.println("Proxy: 請求后的收尾");}
}
  1. 使用示例
public class Client {public static void main(String[] args) {Subject realSubject = new RealSubject();Subject proxy = new Proxy(realSubject);proxy.request(); // 通過代理調用目標對象的方法}
}

1.2 JDK動態代理

  • 靜態代理適用于目標對象較少且接口固定的場景。如果需要動態地為多個對象生成代理,可以使用 JDK 動態代理CGLIB 動態代理

1.2.1 JDK動態代理概述

  • JDK動態代理是一種運行時動態生成代理類的技術,允許在不修改目標類的情況下增強其功能。它主要依賴于 java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler 接口。
  • Proxy: 提供靜態方法用于創建代理實例。
  • InvocationHandler: 定義了代理對象調用方法時的處理邏輯。

1.2.2 JDK動態代理案例代碼

  1. 創建一個接口(如 MyInterface)。
  2. 創建接口的實現類(如 MyInterfaceImpl)。
  3. 實現 InvocationHandler 接口,定義攔截邏輯。
  4. 使用 Proxy.newProxyInstance() 方法生成代理對象。
// 接口類
public interface MyInterface {void doSomething();
}
// 實現類
public class MyInterfaceImpl implements MyInterface{@Overridepublic void doSomething() {System.out.println("Do something");}
}import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//代理類
public class MyInvocationHandler implements InvocationHandler {private final Object target;public MyInvocationHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method call");Object result = method.invoke(target, args);System.out.println("After method call");return result;}}
//測試類
public static void main(String[] args) {// 創建目標對象(真實主題),它是 MyInterface 接口的具體實現類MyInterface target = new MyInterfaceImpl();// 創建一個 InvocationHandler 實例,并將目標對象傳入,用于處理代理對象的方法調用InvocationHandler handler = new MyInvocationHandler(target);// 使用 Proxy.newProxyInstance() 方法創建一個代理對象// 參數說明:// 1. target.getClass().getClassLoader():指定類加載器,用來加載動態生成的代理類// 2. new Class<?>[] { MyInterface.class }:指定代理類要實現的接口列表,這里只有一個接口 MyInterface// 3. handler:指定代理對象的方法調用處理器,即當代理對象的方法被調用時,會轉交給這個 handler 來處理MyInterface proxy = (MyInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 類加載器// 要代理的接口 使用new Class<?>[] { MyInterface.class },或target.getClass().getInterfaces(),target.getClass().getInterfaces(),handler // 方法調用處理器);// 調用代理對象的方法,實際會被 InvocationHandler 的 invoke 方法攔截并處理proxy.doSomething();
}

1.2.3 JDK動態代理的應用場景

  • AOP編程:如日志記錄、性能監控等。
  • 遠程調用:如RMI(Remote Method Invocation)。
  • 權限控制:在調用目標方法前進行權限驗證。

1.2.4 JDK動態代理的特點

  • 基于接口:只能對接口進行代理,不能對類進行代理。
  • 動態生成字節碼:在運行時動態生成代理類的字節碼。
  • 靈活性高:可以在不修改目標類的情況下增強其功能。

1.2.5 與創建型模式的區別

  • 創建型模式(如工廠模式、單例模式、建造者模式等)關注的是對象的創建方式,隱藏對象的構造細節。
  • JDK動態代理屬于行為型模式,關注的是對象之間的交互和職責分配。

1.3 CGLIB動態代理

1.3.1 CGLIB動態代理概述

  • CGLIB(Code Generation Library)是一個強大的字節碼生成庫,常用于在運行時動態生成類。它在Java中廣泛應用于AOP編程、代理模式實現、ORM框架等場景。
  • CGLIB動態代理的設計結構與使用方式,屬于結構型設計模式的一種變體(非GoF經典23種之一,但基于代理模式的擴展)。
  • CGLIB動態代理是基于繼承機制實現的代理方式,適用于沒有接口的類。它是Spring AOP底層的重要技術支撐之一,具有廣泛的工程應用價值。

  • CGLIB動態代理的核心概念
  1. Enhancer 類:

    • 是CGLIB中最核心的類,用來創建一個被代理類的子類。
    • 通過設置回調(Callback),攔截方法調用。
  2. MethodInterceptor 接口:

    • 用戶自定義的攔截器,必須實現 intercept() 方法。
    • 在該方法中可以加入增強邏輯(如日志、事務、權限控制等)。
  3. 被代理類(Target Class):

    • 不需要實現接口,這是與JDK動態代理的主要區別。
    • CGLIB通過繼承方式生成子類來實現代理。

1.3.2 CHLIB動態代理代碼案例

  1. 需要引入依賴(Maven):
  <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
  1. 業務代碼
public class UserService {public void addUser() {System.out.println("添加用戶");}
}
  1. 實現 MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("前置增強邏輯");Object result = proxy.invokeSuper(obj, args); // 調用父類原始方法System.out.println("后置增強邏輯");return result;}
}
  1. 使用 Enhancer 創建代理對象
import net.sf.cglib.proxy.Enhancer;public class CglibProxyDemo {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class); // 設置父類enhancer.setCallback(new MyMethodInterceptor()); // 設置攔截器UserService proxy = (UserService) enhancer.create(); // 創建代理對象proxy.addUser(); // 調用方法,觸發攔截邏輯}
}
  1. 添加虛擬接參數解決報錯
--add-opens java.base/java.lang=ALL-UNNAMED

在這里插入圖片描述
5. 執行結果

前置增強邏輯
添加用戶
后置增強邏輯

1.4 CGLIB vs JDK 動態代理對比

特性CGLIB動態代理JDK動態代理
原理繼承目標類生成子類實現接口生成代理
是否需要接口
性能初次生成較慢,運行快每次反射調用較慢
應用場景Spring AOP(無接口)、Hibernate懶加載等Spring AOP(有接口)、RMI

1.5 代理模式的經典應用

  1. Spring中Bean對象的AOP代理。
  2. MyBatis中Mapper接口代理對象。
  3. MyBatis中JDBC的日志代理。
  4. Ribbon或Loadbalancer中的RestTemplate代理對象。
  5. OpenFeign的接口代理對象。

1.6 代理模式的經典應用

  • 代理模式在業務系統中的應用一般都是用來拓展增強業務功能。現在需要統計每個請求到來的處理時長,如果超過500毫秒就給出警告。
//控制層
public class UserController {private UserService userService;public UserController() {this.userService = new UserService(); // 改為構造函數中初始化}public String login() {return userService.login();}
}
// 服務層
public class UserService {public String login(){return "登錄成功";}
}
//測試類
public class StartApp {public static void main(String[] args) {UserController userController = new UserController();UserController proxy = new MethodInterceptor() {public UserController createProxy(Class targetClass) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(targetClass);enhancer.setCallback(this);return (UserController) enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {long start = System.currentTimeMillis();Object res = proxy.invoke(userController, args);long end = System.currentTimeMillis();System.out.println("耗時:" + (end - start));if(end-start>500){System.out.println("耗時過長");}return res;}}.createProxy(UserController.class);proxy.login();}
}

二 裝飾器模式

在這里插入圖片描述

  • 裝飾器模式(Decorator Pattern/包裝模式)是一種結構型設計模式,它允許你通過將對象放入包含行為的特殊封裝對象中來為原對象增加功能。這種模式比靜態子類化更靈活,能夠在運行時動態地給對象添加職責。
  • 裝飾模式是一種用于代替繼承的技術,無需通過繼承子類增加子類就能拓展對象的新功能。使用對象的關聯關系代替繼承關系,更加靈活、同時避免類型體系的快速膨脹。

2.1 裝飾器模式的核心組成

  • Component(抽象組件):定義對象和裝飾器的公共接口。
  • ConcreteComponent(具體組件):實現基礎功能的對象。
  • Decorator(抽象裝飾器):繼承或實現Component,并持有Component對象的引用。
  • ConcreteDecorator(具體裝飾器):為對象增加具體的功能。

2.2 裝飾器模式的代碼案例

  1. 抽象組件 Coffee
public interface Coffee {double cost();String description();
}
  1. 具體組件 SimpleCoffee
public class SimpleCoffee implements Coffee {@Overridepublic double cost() {return 2.0;}@Overridepublic String description() {return "Simple Coffee";}
}
  1. 抽象裝飾器 CoffeeDecorator
public abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee coffee) {this.decoratedCoffee = coffee;}@Overridepublic double cost() {return decoratedCoffee.cost();}@Overridepublic String description() {return decoratedCoffee.description();}
}
  1. 具體裝飾器 MilkDecorator
public class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 0.5; // 加牛奶的價格}@Overridepublic String description() {return super.description() + ", Milk";}
}
  1. 具體裝飾器 SugarDecorator
public class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 0.2; // 加糖的價格}@Overridepublic String description() {return super.description() + ", Sugar";}
}
  1. 使用示例
public class Main {public static void main(String[] args) {Coffee coffee = new SimpleCoffee();System.out.println("Cost: $" + coffee.cost() + " | Description: " + coffee.description());Coffee milkCoffee = new MilkDecorator(new SimpleCoffee());System.out.println("Cost: $" + milkCoffee.cost() + " | Description: " + milkCoffee.description());Coffee sugarMilkCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));System.out.println("Cost: $" + sugarMilkCoffee.cost() + " | Description: " + sugarMilkCoffee.description());}
}
  1. 輸出結果
Cost: $2.0 | Description: Simple Coffee
Cost: $2.5 | Description: Simple Coffee, Milk
Cost: $2.7 | Description: Simple Coffee, Milk, Sugar

2.3 裝飾器模式的優點

  • 靈活性高:相比靜態繼承方式,裝飾器模式更加靈活,可以在運行時動態地組合對象和功能。
  • 開閉原則:無需修改原有代碼即可擴展功能。
  • 組合優于繼承:避免了類爆炸的問題,多個裝飾器可以自由組合使用。

2.4 裝飾器模式的適用場景

  • 當需要動態、透明地給對象添加職責時。
  • 當子類擴展不切實際或會導致類爆炸時。
  • 當希望保持類責任清晰,并避免復雜的繼承關系時。

三 適配器模式

  • 適配器模式(Adapter Pattern)是一種結構型設計模式,它允許將一個類的接口轉換成客戶端期望的另一個接口。適配器模式常用于解決兩個不兼容接口之間的適配問題。
  • 適配器模式的作用是把兩個不兼容的對象通過適配器連接起來工作。
  • 在 Java 中,java.util.Arrays.asList() 可以將數組轉換為 List,這其實也是一種適配器模式。
  • Spring 框架中也廣泛使用適配器模式來適配不同的事件監聽器、處理器等組件。

3.1 適配器模式的主要角色

  • 目標接口(Target):定義客戶端使用的接口。
  • 被適配者(Adaptee):需要被適配的現有類,其接口與目標接口不兼容。
  • 適配器(Adapter):實現目標接口,并持有被適配者的實例,通過組合或繼承的方式完成接口轉換。
    在這里插入圖片描述

3.2 適配器模式的兩種實現方式

  • 對象適配器(推薦):使用組合的方式,適配器包含被適配者的實例。
  • 類適配器:使用繼承的方式,適配器繼承自被適配者并實現目標接口。

3.3 適配器模式的代碼案例

3.3.1 目標接口 Target

public interface Target {void request();
}

3.3.2 被適配者 Adaptee

public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specific request.");}
}

3.3.3 對象適配器 ObjectAdapter

public class ObjectAdapter implements Target {private Adaptee adaptee;public ObjectAdapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specificRequest();}
}

3.3.4 類適配器 ClassAdapter(如果使用繼承)

public class ClassAdapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest();}
}

3.3.5 客戶端代碼

public class Client {public static void main(String[] args) {// 使用對象適配器Adaptee adaptee = new Adaptee();Target target = new ObjectAdapter(adaptee);target.request();  // 輸出: Adaptee's specific request.// 使用類適配器Target classAdapter = new ClassAdapter();classAdapter.request();  // 輸出: Adaptee's specific request.}
}

3.4 適配器模式的應用場景

  • 當希望復用已有的類,但其接口不符合當前需求時。
  • 當需要在不影響現有代碼的情況下擴展功能時。
  • 當多個子類有不同的接口,而客戶端希望統一調用時。

3.5 適配器模式的優缺點


優點

  • 提高了類的復用性,避免對已有代碼進行修改。
  • 解耦客戶端和被適配者,使得兩者可以獨立變化。
  • 符合開閉原則,新增適配器不會影響原有系統。

缺點

  • 增加系統的復雜度,引入額外的適配類。
  • 如果過度使用,可能導致系統難以理解和維護。

四 組合模式

  • 組合模式(Composite Pattern)是一種結構型設計模式,它允許你將對象組合成樹形結構來表示“部分-整體”的層次關系。通過組合模式,客戶端可以統一地處理單個對象和對象的組合。
  • 組合模式的核心在于統一處理葉子節點(Leaf)和組合節點(Composite)。它讓客戶端無需關心當前操作的是單個對象還是組合對象,從而簡化了客戶端代碼。
  • 組合模式非常適合用于構建具有遞歸結構的對象樹,尤其在需要統一處理個體和群體的情況下非常有用。

4.1 組合模式的主要角色

  • Component:抽象類或接口,定義葉子節點和組合節點的公共行為。
  • Leaf:表示葉子節點,沒有子節點,實現基礎功能。
  • Composite:表示組合節點,包含子組件(可以是葉子節點或其他組合節點),并實現了管理子組件的方法(如添加、移除等)。

4.2 組合模式的UML 結構圖

        +-----------+| Component |+-----------+| operation()|+-----------+/     \/       \/         \
+--------+    	+-------------+
|   Leaf   |    | Composite   |
+----------+    +-------------+
| operation()|  | operation() || add(Component)|| remove(Component)|| getChild(int)  |+---------------+

4.3 組合模式的代碼案例

4.3.1 定義 Component 接口

public interface Component {void operation();
}

4.3.2 實現 Leaf

public class Leaf implements Component {private String name;public Leaf(String name) {this.name = name;}@Overridepublic void operation() {System.out.println("Leaf " + name + " is doing operation.");}
}

4.3.3 實現 Composite

import java.util.ArrayList;
import java.util.List;public class Composite implements Component {private String name;private List<Component> children = new ArrayList<>();public Composite(String name) {this.name = name;}public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}@Overridepublic void operation() {System.out.println("Composite " + name + " is doing operation.");for (Component child : children) {child.operation();}}
}

4.3.4 客戶端使用

public class Client {public static void main(String[] args) {// 創建葉子節點Component leaf1 = new Leaf("Leaf1");Component leaf2 = new Leaf("Leaf2");// 創建組合節點Composite composite = new Composite("Composite1");composite.add(leaf1);composite.add(leaf2);// 調用組合的操作composite.operation();}
}
  • 輸出結果
Composite Composite1 is doing operation.
Leaf Leaf1 is doing operation.
Leaf Leaf2 is doing operation.

4.4 組合模式的適用場景

  • 表示“部分-整體”的樹形結構。
  • 希望客戶端能夠忽略組合對象與單個對象的不同,統一處理它們。
  • 需要動態地構建層級結構,并希望靈活地增刪結構中的元素。

4.5 組合模式的優缺點


優點

  • 符合開閉原則,增加新的組件類不需要修改現有代碼。
  • 簡化客戶端代碼,使客戶端更容易操作復雜結構。
  • 提供一致的方式處理單個對象和組合對象。

缺點

  • 如果過度使用組合模式,可能會導致系統中出現大量細小的對象,增加調試和維護成本。
  • 對于不支持樹形結構的操作,需要額外處理。

五 門面模式

  • 門面模式(Facade Pattern/外觀模式)是 Java 中常用的 結構型設計模式 之一,它的主要目的是為子系統中的一組接口提供一個統一的高層接口,使得子系統更容易被使用。
  • 門面模式是一種非常實用的設計模式,特別適用于需要將復雜的子系統簡化為一個統一接口的場景。它通過引入一個外觀類,將客戶端與子系統的復雜性隔離開來,從而提高了系統的可維護性和易用性。
    在這里插入圖片描述

5.1 門面模式的核心思想

  • 簡化復雜系統的調用方式:通過引入一個外觀類(Facade),將復雜的調用流程封裝起來,讓客戶端只需要與外觀類交互。
  • 降低耦合度:客戶端不直接依賴子系統的具體實現,而是通過外觀類間接訪問,提高了模塊化和可維護性。

5.2 門面模式的主要角色

  1. 子系統類(SubSystem):實現了系統內部的具體功能。
  2. 外觀類(Facade):提供一個統一的接口,封裝子系統的調用邏輯。
  3. 客戶端(Client):使用外觀類來完成對子系統的操作。

5.3 門面模式的代碼案例

// 子系統類 A
class SubSystemA {public void operationA() {System.out.println("SubSystemA: 操作 A");}
}// 子系統類 B
class SubSystemB {public void operationB() {System.out.println("SubSystemB: 操作 B");}
}// 子系統類 C
class SubSystemC {public void operationC() {System.out.println("SubSystemC: 操作 C");}
}// 外觀類
class Facade {private SubSystemA subSystemA;private SubSystemB subSystemB;private SubSystemC subSystemC;public Facade() {this.subSystemA = new SubSystemA();this.subSystemB = new SubSystemB();this.subSystemC = new SubSystemC();}// 高層接口public void operation() {subSystemA.operationA();subSystemB.operationB();subSystemC.operationC();}
}// 客戶端
public class Client {public static void main(String[] args) {Facade facade = new Facade();facade.operation(); // 調用外觀類的方法}
}
  • 輸出結果
SubSystemA: 操作 A
SubSystemB: 操作 B
SubSystemC: 操作 C

5.4 門面模式的應用場景

  1. 簡化接口:當需要隱藏復雜的子系統時,可以通過門面模式提供一個更簡單的接口。
  2. 分層架構設計:在分層系統中,每一層通過門面暴露其服務給上層。
  3. 遺留系統封裝:當需要集成舊系統時,可以使用門面模式將其封裝成一個新接口。
  4. 解耦:減少客戶端與子系統的直接依賴,提高代碼的靈活性。

5.5 門面模式的優缺點


優點

  • 簡化客戶端調用邏輯。
  • 降低系統間的耦合度。
  • 更容易維護和擴展。

缺點

  • 不符合開閉原則:如果新增子系統或修改現有子系統,可能需要修改外觀類的代碼。

六 橋接模式

  • 橋接模式(Bridge Pattern)是一種結構型設計模式,用于將抽象部分與其具體實現部分分離,使它們可以獨立變化。它通過組合的方式替代繼承,避免類爆炸的問題。

6.1 橋接模式的主要角色

  • 橋接模式包含以下核心角色:
  1. Abstraction(抽象類)

    • 定義抽象類的接口,并持有對 Implementor 的引用。
    • 通常是一個高層次的抽象,不直接實現功能,而是委托給 Implementor
  2. RefinedAbstraction(擴展抽象類)

    • 擴展 Abstraction 類的功能,增加更復雜的業務邏輯。
  3. Implementor(實現接口)

    • 定義實現部分的接口,供 Abstraction 調用。
    • 通常是不同平臺或方式下的統一接口。
  4. ConcreteImplementorA / ConcreteImplementorB(具體實現類)

    • 實現 Implementor 接口的具體行為。

6.2 橋接模式的應用場景

  • 避免在兩個獨立維度上使用多重繼承導致類數量爆炸。
  • 抽象和其實現都要通過子類擴展時,希望解耦兩者的繼承關系。
  • 運行時可以切換實現的情況。

6.3 橋接模式的代碼案例

// Implementor
interface Implementor {void operationImpl();
}// ConcreteImplementorA
class ConcreteImplementorA implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorA operation");}
}// ConcreteImplementorB
class ConcreteImplementorB implements Implementor {public void operationImpl() {System.out.println("ConcreteImplementorB operation");}
}// Abstraction
abstract class Abstraction {protected Implementor implementor;protected Abstraction(Implementor implementor) {this.implementor = implementor;}public abstract void operation();
}// RefinedAbstraction
class RefinedAbstraction extends Abstraction {public RefinedAbstraction(Implementor implementor) {super(implementor);}public void operation() {System.out.print("RefinedAbstraction: ");implementor.operationImpl();}
}//測試類
public class Client {public static void main(String[] args) {Implementor implA = new ConcreteImplementorA();Implementor implB = new ConcreteImplementorB();Abstraction abstrA = new RefinedAbstraction(implA);Abstraction abstrB = new RefinedAbstraction(implB);abstrA.operation(); // 輸出:RefinedAbstraction: ConcreteImplementorA operationabstrB.operation(); // 輸出:RefinedAbstraction: ConcreteImplementorB operation}
}

6.4 橋接模式的優缺點

優點

  • 解耦抽象與實現:允許兩者獨立變化。
  • 提高可擴展性:新增一個維度只需擴展,不需修改。
  • 避免類爆炸問題:通過組合代替繼承。

缺點

  • 增加系統理解難度:需要正確識別兩個獨立變化的維度。
  • 接口設計要求高:必須提前規劃好 ImplementorAbstraction 的接口。

6.5 橋接模式的經典應用

  • 不同操作系統上的圖形繪制庫(圖形是抽象,渲染是實現)。
  • 多種支付渠道(微信、支付寶)與多種支付方式(掃碼、刷臉)的組合。
  • 消息通知系統:消息類型(文本、圖片)與發送渠道(短信、郵件、站內信)的組合。
  • JDBC(java與數據庫交互的API)將java應用程序與不同數據庫之間的連接進行解耦。JDBC提供標準的接口作為抽象部分,具體的數據庫驅動程序作為實現部分。
  • SLF4J日志框架提供Logger,Appender和Formatter。他們三個表示不同的維度。Logger表示日志記錄所屬的類,Appender表示日志的輸出,Formatter表示日志記錄的格式。三個維度可以有多種不同的實現,利用橋接模式,可以實現三種維度的任意組合。

七 享元模式

  • 享元模式(Flyweight Pattern)是 Java 中常用的結構型設計模式之一,主要用于減少創建和管理大量相似對象時的內存開銷。

  • 享元模式的核心思想:通過共享技術來有效支持大量細粒度的對象的重用。適用于:對象數量巨大;大部分對象的狀態可以外部化(即對象之間差異小)

  • 享元對象能做到共享的關鍵是區分了內部狀態和外部狀態。
    • 內部狀態:可以共享,不會隨環境的改變而變化
    • 外部狀態:不可以共享,會隨環境的改變而變化
  • 例如象棋上棋子的顏色,形狀,大小是共享屬性(內部狀態),而位置是不可共享屬性(外部狀態)。

7.1 享元模式的主要角色

角色描述
Flyweight(抽象享元類)定義公共接口或抽象類,包含一個operation()方法。
ConcreteFlyweight(具體享元類)實現抽象享元接口,存儲內部狀態(intrinsic state)。
UnsharedConcreteFlyweight(非共享享元類)不參與共享的享元類,通常用于組合結構。
FlyweightFactory(享元工廠類)負責創建和管理享元對象,通常使用 Map 緩存已創建的享元對象。

7.2享元模式的代碼實例

7.2.1 抽象享元類

public interface Flyweight {void operation(String extrinsicState); // extrinsicState 是外部狀態
}

7.2.2 具體享元類

public class ConcreteFlyweight implements Flyweight {private String intrinsicState; // 內部狀態,可共享public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}@Overridepublic void operation(String extrinsicState) {System.out.println("內部狀態: " + intrinsicState + ", 外部狀態: " + extrinsicState);}
}

7.2.3 享元工廠類

import java.util.HashMap;
import java.util.Map;public class FlyweightFactory {private Map<String, Flyweight> flyweights = new HashMap<>();public Flyweight getFlyweight(String key) {if (!flyweights.containsKey(key)) {flyweights.put(key, new ConcreteFlyweight(key));}return flyweights.get(key);}public int getTotalFlyweights() {return flyweights.size();}
}

7.2.4 客戶端調用

public class Client {public static void main(String[] args) {FlyweightFactory factory = new FlyweightFactory();Flyweight f1 = factory.getFlyweight("A");Flyweight f2 = factory.getFlyweight("B");Flyweight f3 = factory.getFlyweight("A"); // 共享已有對象f1.operation("X");f2.operation("Y");f3.operation("Z");System.out.println("實際創建的享元對象數量:" + factory.getTotalFlyweights());}
}

  • 輸出結果
內部狀態: A, 外部狀態: X
內部狀態: B, 外部狀態: Y
內部狀態: A, 外部狀態: Z
實際創建的享元對象數量:2

7.3享元模式的優缺點


優點

  • 節省內存:通過共享對象,減少重復創建對象的數量。
  • 提高性能:避免頻繁的對象創建與銷毀。

缺點

  • 對JVM回收不友好,因為工廠類一直保持對享元類的引用,造成享元類在沒有任何引用的情況下也不會被JVM回收。

7.4 享元模式的適用場景

  • 系統中存在大量相似對象,如字符、圖形、連接池等。需要將對象的狀態分為內部狀態外部狀態
    • 內部狀態:不變且可共享(如字體名稱)
    • 外部狀態:隨環境變化而變化(如字體大小、顏色)

7.5 享元模式的經典應用

  • 文本編輯器中對字符的處理(每個字符作為享元對象)
  • 游戲開發中的粒子系統,棋類游戲的棋子對象
  • 數據庫連接池、線程池等資源池化管理

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85359.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85359.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85359.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

鴻蒙Harmony測試-wukong穩定性工具(類似Android的Monkey測試)

一、功能介紹 wukong是系統自帶的一種命令行工具&#xff0c;支持Ability的隨機事件注入、控件注入、異常捕獲、報告生成和對Ability數據遍歷截圖等特性。通過模擬用戶行為&#xff0c;對系統或應用進行穩定性壓力測試。wukong分為隨機測試、專項測試和專注測試。 隨機測試是指…

從零學起VIM

前言 筆者早年剛入行的時候就接觸過Vim,當時還是真正的菜鳥&#xff0c;帶我的師父是一個華為骨干員工&#xff0c;猶記得他給我指導如何保存并關閉文本&#xff1a;按Esc&#xff0c;然后輸入:wq。還記得自己打開Vim編輯器&#xff0c;一個字符都敲不進去&#xff0c;然后問旁…

不依賴rerank 模型排序通過使用 PostgreSQL 中的 pgvector 與 tsearch2 函數進行混合搜索提高召回率

前言 在向量搜索中&#xff0c;召回率是一個關鍵指標&#xff0c;它衡量搜索結果的相關性。然而&#xff0c;提高召回率往往會犧牲其他指標&#xff0c;如索引大小或查詢延遲。為了平衡這些權衡&#xff0c;混合搜索技術應運而生。本文將介紹如何在 PostgreSQL 中結合 pgvecto…

Uniapp 跨平臺開發框架全面解析:一次開發,多端運行

在移動互聯網時代&#xff0c;開發者面臨著一個重要挑戰&#xff1a;如何高效地開發出能在多個平臺&#xff08;iOS、Android、Web、小程序等&#xff09;上運行的應用&#xff1f;傳統的原生開發方式需要為每個平臺單獨編寫代碼&#xff0c;導致開發周期長、維護成本高。而 Un…

ios如何把H5網頁變成主屏幕webapp應用

一、將 H5 頁面添加到主屏幕的步驟 打開 Safari 瀏覽器 在 iPhone 上打開 Safari 瀏覽器&#xff0c;訪問目標網頁&#xff08;H5 頁面&#xff09;。 點擊分享按鈕 在 Safari 瀏覽器底部點擊 “分享” 圖標&#xff08;箭頭向上的按鈕&#xff09;。 添加到主屏幕 在分享菜單…

Node.js 項目啟動命令大全 (形象版)

文章目錄 Node.js 項目啟動命令大全 &#x1f31f;?&#xff08;形象版&#xff09;一、&#x1f50d; 如何查看項目啟動命令&#xff08;魔法書目錄&#xff09;package.json scripts 參數詳解開發相關腳本測試相關腳本構建相關腳本代碼質量相關腳本最佳實踐 二、&#x1f68…

愛普特APT32F1104C8T6單片機 高抗干擾+硬件加密雙保障

愛普特APT32F1104C8T6單片機深度解析 1. 產品定位 APT32F1104C8T6 是愛普特半導體&#xff08;APT&#xff09;推出的 32位高性能經濟型單片機&#xff0c;基于 ARM Cortex-M0內核&#xff0c;采用 LQFP48封裝&#xff0c;主打 高性價比、低功耗、強抗干擾&#xff0c;是替代進…

使用uni-app ios 打包流程

配置幾個步驟即可 1、打包ios需要BundleID ID 2、證書私鑰密碼 3、信任文件證書文件 4、私鑰證書 5、打包 6、獲取打包后的ipa文件 7、通過愛思助手安裝到iso手機上 8、完成 1、下載&#xff1a;App Uploader去獲取我們想要的證書私鑰等文件 2、下載完成解壓后的文件如下打…

仿muduo庫實現并發服務器

1.實現目標 仿muduo庫One Thread One Loop式主從Reactor模型實現高并發服務器&#xff1a; 通過實現高并發服務器的組件&#xff0c;可以快速實現一個高并發服務器的搭建&#xff0c;并且&#xff0c;通過組內不同應用層協議的支持&#xff0c;可以快速完成高性能服務器的搭建…

迭代器模式:集合遍歷的統一之道

引言&#xff1a;集合遍歷的演進之路 在軟件開發中&#xff0c;集合遍歷是我們每天都要面對的基礎操作。從最初的數組索引遍歷到現代的流式處理&#xff0c;我們經歷了&#xff1a; #mermaid-svg-KwTr9k8JgbwRTDhU {font-family:"trebuchet ms",verdana,arial,sans-…

Spring Security OAuth2 組件

我們來系統地講解一下 Spring Security OAuth2 這個強大的組件。我會從概念、作用、核心組件&#xff0c;以及實際應用場景來為你剖析。 1. 什么是 Spring Security OAuth2&#xff1f; 簡單來說&#xff0c;Spring Security OAuth2 是 Spring Security 框架的一個模塊&#…

Redis的持久化功能

Redis的持久化功能能夠將內存中的數據保存到磁盤&#xff0c;從而在重啟后恢復數據。下面為你詳細介紹Redis的兩種主要持久化方式及其配置方法。 RDB&#xff08;Redis Database&#xff09;持久化 RDB持久化是通過生成某個時間點的數據集快照來實現的。它具有高性能的特點&a…

Chrome 將成為下一個 IE6

最近在技術圈刷到一個帖子&#xff0c;說&#xff1a;“Chrome 就快變成新的 IE6 了。” 乍一看有點危言聳聽&#xff0c;但你一細品&#xff0c;發現還真挺像回事。 想當年&#xff1a;IE6 是怎么垮的&#xff1f; IE6 當年多風光&#xff1f;全球市場份額一度超過 90%&#…

Redis 配置文件詳解redis.conf 從入門到實戰

一、redis.conf 是什么&#xff1f; Redis 的配置文件&#xff08;默認命名為 redis.conf&#xff0c;Redis 8.0 之后改為 redis-full.conf&#xff09;控制著服務運行的各項參數。該文件采用以下結構&#xff1a; 指令名 參數1 參數2 ... 參數N例如&#xff1a; replicaof …

autoware docker的安裝

前言 官方的安裝說明&#xff1a; 官方的安裝說明 安裝前&#xff0c;請確認安裝的硬件&#xff1a; CPU with 8 cores16GB RAM[Optional] NVIDIA GPU (4GB RAM) 滿足需求 1. 安裝軟件依賴 這一步主要是安裝三個軟件&#xff1a; DockerNVIDIA Container Toolkit (pref…

AWS 解決方案深度剖析:Amazon QLDB — 構建可信賴、不可變的數據審計基石

導言&#xff1a;數據可信的挑戰 在現代應用開發中&#xff0c;尤其是在金融、供應鏈、身份認證、政府事務、醫療記錄管理等領域&#xff0c;數據完整性和歷史追溯性至關重要。我們常常面臨以下挑戰&#xff1a; 審計困難&#xff1a; 如何證明數據從誕生至今未被篡改&#xf…

Leetcode-?1358. 包含所有三種字符的子字符串數目?

Problem: 1358. 包含所有三種字符的子字符串數目 思路 滑動窗口 解題過程 滑動窗口&#xff1a;使用左右指針 l 和 r 維護一個窗口&#xff0c;窗口內字符的頻次由 cnt 記錄。 右指針擴展&#xff1a;右指針 r 不斷右移&#xff0c;將字符加入窗口并更新頻率。 左指針收縮&a…

iTunes 無法備份 iPhone:10 種解決方法

Apple 設備是移動設備市場上最先進的產品之一&#xff0c;但有些人遇到過 iTunes 因出現錯誤而無法備份 iPhone 的情況。iTunes 拒絕備份 iPhone 時&#xff0c;可能會令人非常沮喪。不過&#xff0c;幸運的是&#xff0c;我們有 10 種有效的方法可以解決這個問題。您可以按照以…

Unity 接入抖音小游戲一

目錄 一、搭建小游戲環境 二、接入抖音SDK 1.初始化 2.登錄 3.分享 4.添加到桌面 5.側邊欄功能 6. 接入流量主 三、完整代碼 下一篇傳送門 Unity 接入抖音小游戲二 -CSDN博客 一、搭建小游戲環境 我這邊因為沒有下載其他版本的Unity所以就先用2022.3.57f1了 大家還是下載…

Node.js 項目啟動命令全面指南:從入門到精通(術語版)

文章目錄 Node.js 項目啟動命令全面指南&#xff1a;從入門到精通一、核心啟動命令深度解析1. 基礎命令結構與執行機制2. 參數傳遞機制詳解 二、常用命令分類詳解1. 運行環境命令對比2. 質量保障命令詳解3. 構建部署全流程 三、高級配置實戰技巧1. 環境變量管理進階2. 命令組合…