设计模式2-结构型模式

news/2025/10/23 14:37:38/文章来源:https://www.cnblogs.com/smalldong/p/19160513

设计模式2-结构型模式

作用:通过合理组合类或对象,优化系统的结构(如类的继承关系、对象的关联关系),实现功能复用、结构灵活或降低复杂度。

2.1 桥接模式(Bridge Pattern)

一、什么是桥接模式?

桥接模式是 ** 结构型模式中用于 “解耦抽象与实现”** 的核心模式,其核心思想是:将抽象部分(Abstraction)与实现部分(Implementation)分离,使它们可以独立地变化,且两者通过 “桥接”(引用关系)连接

简单说:“把两个独立变化的维度拆分开,用‘桥’(关联关系)替代‘继承’,避免类爆炸”

这里的 “抽象” 指定义核心业务逻辑的类(如 “手机”),“实现” 指抽象类所依赖的具体功能(如 “手机软件”);“桥接” 则是抽象类中持有实现类的引用,通过委托调用实现功能,而非通过继承硬编码。

二、为什么需要桥接模式?(作用)

当一个系统存在两个或多个独立变化的维度(如 “手机品牌” 和 “手机软件”,“形状” 和 “颜色”),且维度之间需要组合时,用继承会导致 “类爆炸”(组合数量 = 维度 1 数量 × 维度 2 数量)。桥接模式的核心作用是:

  1. 解决类爆炸问题:将多维度的组合关系从 “继承层级” 转为 “关联关系”,减少类的数量(从M×N减少为M+N)。
  2. 实现维度独立扩展:每个维度可单独扩展(如新增手机品牌或新增手机软件),无需修改原有代码,符合 “开闭原则”。
  3. 分离抽象与实现:抽象部分(如手机)专注于核心逻辑,实现部分(如软件)专注于具体功能,职责更清晰。

三、反例:用继承处理多维度的问题

假设我们要设计一个手机系统,包含 “手机品牌”(如华为、小米)和 “手机软件”(如游戏、通讯录)两个维度,每个品牌的手机都需要支持这些软件。

用继承实现的缺陷:

// 1. 抽象手机类
abstract class Phone {public abstract void run(); // 运行软件
}// 2. 华为手机+具体软件(继承组合)
class HuaweiGamePhone extends Phone {@Overridepublic void run() {System.out.println("华为手机运行游戏");}
}
class HuaweiContactsPhone extends Phone {@Overridepublic void run() {System.out.println("华为手机运行通讯录");}
}// 3. 小米手机+具体软件(继承组合)
class XiaomiGamePhone extends Phone {@Overridepublic void run() {System.out.println("小米手机运行游戏");}
}
class XiaomiContactsPhone extends Phone {@Overridepublic void run() {System.out.println("小米手机运行通讯录");}
}// 客户端使用
public class Client {public static void main(String[] args) {Phone huaweiGame = new HuaweiGamePhone();huaweiGame.run(); // 华为手机运行游戏}
}

问题分析:

  • 类爆炸:若有M个品牌和N个软件,需要创建M×N个子类(如 2 个品牌 ×2 个软件 = 4 个类)。若扩展到 5 个品牌 ×10 个软件,则需要 50 个类,系统极度臃肿;
  • 扩展困难:新增品牌(如苹果)需为每个软件新增子类(AppleGamePhoneAppleContactsPhone...);新增软件(如浏览器)需为每个品牌新增子类(HuaweiBrowserPhoneXiaomiBrowserPhone...),严重违反 “开闭原则”;
  • 职责混乱:子类同时承担 “品牌” 和 “软件” 的职责(如HuaweiGamePhone既管华为的特性,又管游戏的逻辑),耦合过高。
四、正例:用桥接模式解决问题

核心改进:将 “手机品牌”(抽象维度)和 “手机软件”(实现维度)拆分为两个独立层次,通过 “桥接”(品牌持有软件的引用)实现组合,而非继承

桥接模式的实现:

// 1. 定义“实现维度”接口(软件):具体功能的抽象
interface Software {void run(); // 软件运行逻辑
}// 2. 具体实现类(不同软件)
class Game implements Software {@Overridepublic void run() {System.out.println("运行游戏");}
}
class Contacts implements Software {@Overridepublic void run() {System.out.println("运行通讯录");}
}
// 新增软件:浏览器(无需修改其他类)
class Browser implements Software {@Overridepublic void run() {System.out.println("运行浏览器");}
}// 3. 定义“抽象维度”类(手机):持有实现维度的引用(桥接的核心)
abstract class Phone {// 桥接:抽象类关联实现接口(而非继承)protected Software software;// 通过构造函数注入具体软件public Phone(Software software) {this.software = software;}// 抽象方法:手机运行软件(委托给实现维度)public abstract void runSoftware();
}// 4. 扩展抽象类(不同品牌手机)
class HuaweiPhone extends Phone {public HuaweiPhone(Software software) {super(software);}@Overridepublic void runSoftware() {System.out.print("华为手机:");software.run(); // 委托给软件的run()方法}
}
class XiaomiPhone extends Phone {public XiaomiPhone(Software software) {super(software);}@Overridepublic void runSoftware() {System.out.print("小米手机:");software.run();}
}
// 新增品牌:苹果手机(无需修改其他类)
class IPhone extends Phone {public IPhone(Software software) {super(software);}@Overridepublic void runSoftware() {System.out.print("苹果手机:");software.run();}
}// 5. 客户端:自由组合品牌和软件
public class Client {public static void main(String[] args) {// 华为手机+游戏Phone huaweiGame = new HuaweiPhone(new Game());huaweiGame.runSoftware(); // 华为手机:运行游戏// 小米手机+通讯录Phone xiaomiContacts = new XiaomiPhone(new Contacts());xiaomiContacts.runSoftware(); // 小米手机:运行通讯录// 苹果手机+浏览器(新增组合,无需修改原有类)Phone iPhoneBrowser = new IPhone(new Browser());iPhoneBrowser.runSoftware(); // 苹果手机:运行浏览器}
}

改进效果:

  1. 解决类爆炸:类数量从M×N减少为M+N(2 个品牌 + 3 个软件 = 5 个类,而非 6 个继承组合类),扩展后优势更明显(5 个品牌 + 10 个软件 = 15 个类,而非 50 个);

  2. 维度独立扩展:

    • 新增品牌(如IPhone):只需继承Phone,无需修改软件类;

    • 新增软件(如Browser):只需实现Software,无需修改品牌类;

      完全符合 “开闭原则”;

  3. 职责清晰:品牌类(HuaweiPhone)专注于品牌特性,软件类(Game)专注于软件逻辑,通过 “桥接”(software引用)协同工作,耦合度低;

  4. 组合灵活:客户端可动态组合品牌和软件(如 “华为 + 浏览器”“苹果 + 游戏”),无需提前定义所有组合类。

五、总结

桥接模式的核心是 “拆分维度,用桥接关联替代继承组合”,通过将抽象层与实现层分离,解决了多维度场景下的类爆炸问题,同时支持各维度独立扩展。

它的关键是准确识别系统中的独立变化维度(如 “品牌” 和 “功能”),并通过抽象类持有实现接口的引用,形成 “桥” 式连接。实际开发中,当发现类的数量随维度组合呈指数增长时,桥接模式往往是最佳解决方案。

记住:桥接模式让多维度的系统 “拆得开、合得拢、扩得展”

2.2 代理模式(Proxy Pattern)

一、什么是代理模式?

代理模式是 ** 结构型模式中用于 “控制对象访问”** 的核心模式,其核心思想是:为某一对象(目标对象)提供一种代理对象,通过代理对象间接访问目标对象,从而在访问前后添加额外操作(如权限校验、日志记录)或控制访问时机(如延迟加载)

简单说:“代理就像‘中介’,你想访问目标对象?先经过我,我帮你处理一些额外的事,再把请求传给目标”

日常生活中,租房中介是房东的 “代理”(租客通过中介找房东租房,中介负责筛选租客、签订合同);明星经纪人是明星的 “代理”(商务合作先经经纪人对接,经纪人负责谈判、筛选资源),都是代理模式的体现。

二、为什么需要代理模式?(作用)

直接访问目标对象可能导致 “访问逻辑与业务逻辑耦合”(如每次调用方法都要手动加日志)、“无法控制访问权限”(如随意调用敏感方法)或 “目标对象创建成本高”(如提前加载大对象)。代理模式的核心作用是:

  1. 控制访问:决定是否允许客户端访问目标对象(如权限校验,无权限则拒绝)。
  2. 增强功能:在访问目标对象的前后添加通用逻辑(如日志记录、性能监控、事务管理),且不修改目标对象代码(符合 “开闭原则”)。
  3. 延迟初始化:延迟创建目标对象(如大对象或耗时对象,直到真正需要时才创建,节省资源)。
  4. 隐藏实现:客户端无需知道目标对象的具体实现,只需与代理交互,降低耦合。

三、反例:没有代理模式的问题

假设我们要设计一个 “图片加载器”,需要在加载图片前后记录日志,且只有管理员有权限加载高清图片。

不使用代理模式的实现:

// 目标对象:图片加载器
class ImageLoader {// 加载图片(业务逻辑)public void loadHighResolution(String imagePath) {// 业务逻辑:实际加载高清图片(耗时操作)System.out.println("加载高清图片:" + imagePath);}
}// 客户端:直接访问目标对象(问题核心)
public class Client {public static void main(String[] args) {ImageLoader loader = new ImageLoader();String userRole = "guest"; // 当前用户是游客(无权限)// 问题1:权限校验逻辑散落在客户端,重复且难维护if ("admin".equals(userRole)) {// 问题2:日志逻辑与业务逻辑耦合,每次调用都要写System.out.println("日志:开始加载图片");loader.loadHighResolution("photo.jpg");System.out.println("日志:图片加载完成");} else {System.out.println("错误:无权限加载高清图片");}}
}

问题分析:

  • 逻辑耦合:权限校验、日志记录等 “非业务逻辑” 与客户端代码耦合,若多个地方需要调用loadHighResolution,则这些逻辑会重复编写(违反 “DRY 原则”);
  • 修改困难:若需修改日志格式(如添加时间戳)或权限规则(如新增 “VIP” 角色),需修改所有调用处的代码,维护成本高;
  • 目标对象暴露:客户端直接持有ImageLoader实例,可能绕过权限校验直接调用,存在安全风险;
  • 无法延迟加载ImageLoader可能是一个大对象(如初始化时需加载配置),但客户端创建后可能暂时不用,导致资源浪费。

四、正例:用代理模式解决问题

核心改进:创建 “代理类”,由代理类持有目标对象的引用,统一处理权限校验、日志记录等逻辑,客户端只与代理交互

代理模式的基本实现(静态代理)
// 1. 抽象主题:定义目标对象和代理的共同接口(核心,保证代理与目标一致性)
interface Image {void loadHighResolution(String imagePath);
}// 2. 真实主题(目标对象):专注于业务逻辑
class RealImageLoader implements Image {@Overridepublic void loadHighResolution(String imagePath) {// 只负责核心业务:加载图片System.out.println("加载高清图片:" + imagePath);}
}// 3. 代理类:持有目标对象,控制访问并增强功能
class ImageProxy implements Image {// 持有目标对象的引用(代理的核心)private RealImageLoader realLoader; private String userRole; // 当前用户角色(用于权限校验)// 构造函数:传入用户角色,延迟初始化目标对象public ImageProxy(String userRole) {this.userRole = userRole;// 目标对象暂不创建,直到真正需要时(延迟加载)this.realLoader = null; }@Overridepublic void loadHighResolution(String imagePath) {// 1. 访问前增强:权限校验if (!"admin".equals(userRole)) {System.out.println("代理拦截:无权限加载高清图片(用户角色:" + userRole + ")");return;}// 2. 延迟初始化目标对象(需要时才创建)if (realLoader == null) {realLoader = new RealImageLoader();System.out.println("代理:初始化图片加载器(延迟加载)");}// 3. 访问前增强:日志记录System.out.println("代理日志:开始加载图片 -> " + imagePath);// 4. 调用目标对象的业务方法(核心)realLoader.loadHighResolution(imagePath);// 5. 访问后增强:日志记录System.out.println("代理日志:图片加载完成 -> " + imagePath);}
}// 4. 客户端:只与代理交互,无需关心目标对象
public class Client {public static void main(String[] args) {// 客户端创建代理(传入用户角色),不直接接触RealImageLoaderImage proxy = new ImageProxy("admin"); // 管理员角色proxy.loadHighResolution("photo.jpg"); // 输出:// 代理:初始化图片加载器(延迟加载)// 代理日志:开始加载图片 -> photo.jpg// 加载高清图片:photo.jpg// 代理日志:图片加载完成 -> photo.jpg// 测试无权限场景Image guestProxy = new ImageProxy("guest");guestProxy.loadHighResolution("secret.jpg"); // 输出:代理拦截:无权限加载高清图片(用户角色:guest)}
}

改进效果:

  1. 逻辑解耦:权限校验、日志记录等通用逻辑被统一放在代理类中,客户端和目标对象无需关心,避免重复代码;
  2. 控制访问:代理拦截所有对目标对象的访问,确保只有授权用户才能调用,安全性提升;
  3. 延迟加载:目标对象(RealImageLoader)在真正需要时才创建,避免提前占用资源;
  4. 易于维护:修改日志格式或权限规则时,只需修改代理类,客户端和目标对象代码无需变动,符合 “开闭原则”;
  5. 目标对象隐藏:客户端只知道Image接口和代理,不知道RealImageLoader的存在,降低耦合。

五、代理模式的核心结构

代理模式包含 3 个核心角色,通过 “接口” 保证代理与目标的一致性:

  1. 抽象主题(Subject)
    • 定义目标对象和代理类的共同接口(如Image),声明核心业务方法(如loadHighResolution);
    • 客户端通过该接口与代理 / 目标交互,保证 “代理可替代目标”(里氏替换原则)。
  2. 真实主题(Real Subject)
    • 实现Subject接口,是代理所代表的 “目标对象”(如RealImageLoader);
    • 专注于核心业务逻辑(如实际加载图片),不关心访问控制或增强逻辑。
  3. 代理(Proxy)
    • 实现Subject接口,持有Real Subject的引用(如ImageProxy持有RealImageLoader);
    • 负责控制对Real Subject的访问(如权限校验),并在访问前后添加增强逻辑(如日志);
    • 可能延迟初始化Real Subject(需要时才创建)。

六、代理模式的常见类型

根据代理的创建时机和实现方式,代理模式可分为静态代理动态代理

1. 静态代理(如上述示例)
  • 特点:代理类在编译期手动编写,与目标类一一对应(一个目标类对应一个代理类)。
  • 优点:实现简单,逻辑清晰,易于调试;
  • 缺点:代理类需手动编写,若目标类多或方法多,会产生大量重复代码(如为 10 个目标类写代理,每个有 5 个方法,需写 50 个代理方法)。
2. 动态代理(运行时自动生成代理类)

动态代理通过反射在运行时动态生成代理类,无需手动编写代理代码,解决静态代理的 “类爆炸” 问题。常见实现有:

(1)JDK 动态代理(基于接口)

Java 自带的动态代理机制,要求目标类必须实现接口,代理类由java.lang.reflect.Proxy动态生成。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 1. 抽象主题(接口,JDK代理必须)
interface Image {void loadHighResolution(String imagePath);
}// 2. 真实主题
class RealImageLoader implements Image {@Overridepublic void loadHighResolution(String imagePath) {System.out.println("加载高清图片:" + imagePath);}
}// 3. 动态代理处理器(核心:定义增强逻辑)
class ImageInvocationHandler implements InvocationHandler {private Object target; // 目标对象(可通用,不局限于Image)private String userRole;public ImageInvocationHandler(Object target, String userRole) {this.target = target;this.userRole = userRole;}// 所有代理方法的调用都会转发到invoke()@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1. 访问前增强:权限校验if (!"admin".equals(userRole)) {System.out.println("动态代理拦截:无权限访问");return null;}// 2. 访问前增强:日志System.out.println("动态代理日志:调用方法 " + method.getName() + ",参数:" + args[0]);// 3. 调用目标对象的方法Object result = method.invoke(target, args);// 4. 访问后增强:日志System.out.println("动态代理日志:方法 " + method.getName() + " 执行完成");return result;}
}// 4. 客户端:通过Proxy动态生成代理
public class Client {public static void main(String[] args) {// 目标对象Image realLoader = new RealImageLoader();// 创建处理器(传入目标和用户角色)InvocationHandler handler = new ImageInvocationHandler(realLoader, "admin");// 动态生成代理类(参数:类加载器、目标接口、处理器)Image proxy = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(),new Class[]{Image.class},handler);// 调用代理方法(实际执行invoke())proxy.loadHighResolution("dynamic.jpg");// 输出:// 动态代理日志:调用方法 loadHighResolution,参数:dynamic.jpg// 加载高清图片:dynamic.jpg// 动态代理日志:方法 loadHighResolution 执行完成}
}
(2)CGLIB 动态代理(基于继承)

第三方库(需引入 CGLIB 依赖),无需目标类实现接口,通过继承目标类动态生成代理(子类代理父类),适用于无接口的类。

动态代理的核心优势:一个代理处理器可代理多个目标类(如ImageInvocationHandler可同时代理ImageFile等不同接口的类),大幅减少代码量,是框架中常用的代理方式(如 Spring AOP 的核心就是动态代理)。

七、总结

代理模式的核心是 “通过代理对象间接访问目标,控制访问并增强功能”,它将 “访问控制 / 通用逻辑” 与 “核心业务逻辑” 分离,既保护了目标对象,又简化了客户端代码。

实际开发中,静态代理适合简单场景(目标类少、方法少),动态代理(JDK/CGLIB)适合复杂场景(需代理多个类或框架开发)。理解代理模式是掌握 Spring AOP 等框架的基础,也是解耦 “非业务逻辑” 与 “业务逻辑” 的重要手段。

记住:代理模式让 “访问” 更可控,让 “增强” 更灵活

2.3 组合模式(Composite Pattern)教程

一、什么是组合模式?

组合模式是 ** 结构型模式中专门处理 “树形结构”** 的核心模式,其核心思想是:将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,并且使客户端对单个对象(叶子节点)和组合对象(容器节点)的使用具有一致性

简单说:“把单个元素和元素的组合当成同一种东西对待,用统一的方式操作整个树形结构”

日常生活中,文件系统(文件夹包含文件和子文件夹)、公司组织结构(部门包含员工和子部门)、菜单系统(主菜单包含子菜单和菜单项)都是 “部分 - 整体” 的树形结构,组合模式正是为这类场景设计的。

二、为什么需要组合模式?(作用)

当系统中存在 “部分 - 整体” 的树形层次关系(如文件夹与文件、部门与员工)时,直接区分 “单个对象” 和 “组合对象” 会导致客户端代码臃肿、扩展性差。组合模式的核心作用是:

  1. 客户端统一处理:客户端无需区分 “叶子节点”(单个对象,如文件)和 “容器节点”(组合对象,如文件夹),只需通过统一接口操作,简化代码。
  2. 简化树形结构操作:对整个树形结构的操作(如遍历、统计、展示)可通过递归在容器节点中实现,无需客户端手动处理层次关系。
  3. 易于扩展:新增叶子节点或容器节点时,只需实现统一接口,客户端代码无需修改(符合 “开闭原则”)。

三、反例:没有组合模式的问题

假设我们要设计一个文件系统,包含 “文件(File)” 和 “文件夹(Folder)”,文件夹可以包含文件或其他文件夹,需要实现 “展示所有内容” 的功能。

不使用组合模式的实现:

// 1. 单个对象:文件
class File {private String name;public File(String name) { this.name = name; }// 展示文件public void show() {System.out.println("文件:" + name);}
}// 2. 组合对象:文件夹
class Folder {private String name;private List<File> files = new ArrayList<>(); // 只能包含文件private List<Folder> subFolders = new ArrayList<>(); // 只能包含子文件夹public Folder(String name) { this.name = name; }// 添加文件public void addFile(File file) { files.add(file); }// 添加子文件夹public void addFolder(Folder folder) { subFolders.add(folder); }// 展示文件夹内容(需分别处理文件和子文件夹)public void show() {System.out.println("文件夹:" + name);// 展示文件for (File file : files) {file.show();}// 展示子文件夹for (Folder subFolder : subFolders) {subFolder.show();}}
}// 客户端:必须区分文件和文件夹,操作复杂
public class Client {public static void main(String[] args) {// 创建文件File file1 = new File("笔记.txt");File file2 = new File("图片.jpg");// 创建子文件夹Folder subFolder = new Folder("资料");subFolder.addFile(new File("报告.pdf"));// 创建根文件夹Folder root = new Folder("我的文档");root.addFile(file1);root.addFile(file2);root.addFolder(subFolder);// 问题1:客户端需手动调用不同对象的show(),若结构复杂,逻辑混乱root.show(); // 输出:// 文件夹:我的文档// 文件:笔记.txt// 文件:图片.jpg// 文件夹:资料// 文件:报告.pdf// 问题2:若要遍历所有内容,客户端需分别处理File和Folder,代码冗余// (例如统计总数量,需写两个遍历逻辑)}
}

问题分析:

  • 客户端需区分类型:客户端必须知道当前操作的是File还是Folder,调用不同的方法(虽然示例中都有show(),但本质上是两个独立类),若要新增操作(如 “删除”“重命名”),需在两个类中分别实现,客户端也要分别调用;
  • 扩展性差:若新增 “快捷方式”(另一种叶子节点),Folderadd方法需新增addShortcut,且show方法需新增遍历逻辑,违反 “开闭原则”;
  • 树形结构处理复杂:客户端若要遍历整个树形结构(如统计所有文件数量),需手动判断每个节点是叶子还是容器,编写嵌套循环,代码冗长且易出错。

四、正例:用组合模式解决问题

核心改进:定义统一的 “组件接口”,让叶子节点(File)和容器节点(Folder)都实现该接口,容器节点通过递归管理子组件(无论子组件是叶子还是容器),客户端通过统一接口操作所有节点

组合模式的实现:

import java.util.ArrayList;
import java.util.List;// 1. 抽象组件(Component):定义叶子和容器的统一接口(核心)
interface FileSystemComponent {void show(); // 统一的展示方法default void add(FileSystemComponent component) { // 默认实现:叶子节点不支持add,抛出异常throw new UnsupportedOperationException("不支持添加操作"); }default void remove(FileSystemComponent component) {// 默认实现:叶子节点不支持removethrow new UnsupportedOperationException("不支持删除操作");}
}// 2. 叶子节点(Leaf):单个对象,不包含子组件
class File implements FileSystemComponent {private String name;public File(String name) { this.name = name; }@Overridepublic void show() {System.out.println("文件:" + name); // 叶子节点的具体实现}// 无需重写add/remove,使用默认实现(抛出异常)
}// 3. 容器节点(Composite):组合对象,可包含子组件(叶子或其他容器)
class Folder implements FileSystemComponent {private String name;// 存储子组件(统一为FileSystemComponent,无需区分叶子和容器)private List<FileSystemComponent> children = new ArrayList<>();public Folder(String name) { this.name = name; }// 实现add:添加子组件(支持文件或文件夹)@Overridepublic void add(FileSystemComponent component) {children.add(component);}// 实现remove:删除子组件@Overridepublic void remove(FileSystemComponent component) {children.remove(component);}// 实现show:递归展示自身及所有子组件@Overridepublic void show() {System.out.println("文件夹:" + name);// 遍历所有子组件,统一调用show()(无需区分是File还是Folder)for (FileSystemComponent child : children) {child.show(); // 递归调用,自动处理层次结构}}
}// 4. 客户端:通过统一接口操作所有节点,无需区分类型
public class Client {public static void main(String[] args) {// 创建叶子节点(文件)FileSystemComponent file1 = new File("笔记.txt");FileSystemComponent file2 = new File("图片.jpg");// 创建容器节点(子文件夹)FileSystemComponent subFolder = new Folder("资料");subFolder.add(new File("报告.pdf")); // 子文件夹添加文件// 创建根容器(根文件夹)FileSystemComponent root = new Folder("我的文档");root.add(file1);    // 根文件夹添加文件root.add(file2);    // 根文件夹添加文件root.add(subFolder); // 根文件夹添加子文件夹// 客户端统一调用show(),无需关心是文件还是文件夹root.show(); // 输出:// 文件夹:我的文档// 文件:笔记.txt// 文件:图片.jpg// 文件夹:资料// 文件:报告.pdf// 新增操作:统计所有文件数量(通过统一接口递归实现)System.out.println("总文件数:" + countFiles(root)); // 输出:总文件数:3}// 工具方法:递归统计所有文件数量(客户端无需区分节点类型)private static int countFiles(FileSystemComponent component) {if (component instanceof File) {return 1; // 叶子节点(文件)计数1} else {// 容器节点:累加所有子组件的文件数Folder folder = (Folder) component;int count = 0;for (FileSystemComponent child : folder.children) {count += countFiles(child); // 递归统计}return count;}}
}

改进效果:

  1. 客户端统一处理:客户端通过FileSystemComponent接口操作所有节点(file1.show()root.show()),无需区分是File还是Folder,代码简化;

  2. 树形结构自动处理:容器节点(Folder)通过递归调用子组件的show()方法,自动遍历整个树形结构,客户端无需手动处理层次关系;

  3. 扩展性强:

    • 新增叶子节点(如 “快捷方式Shortcut”):只需实现FileSystemComponent接口,Folderaddshow方法无需修改;

    • 新增容器节点(如 “压缩文件夹ZipFolder”):只需继承Folder或实现FileSystemComponent,客户端可直接使用;

      完全符合 “开闭原则”;

  4. 功能复用:新增操作(如countFiles)时,可通过统一接口递归实现,无需为叶子和容器分别编写逻辑。

五、组合模式的核心结构

组合模式的核心是通过 “抽象组件” 统一叶子和容器的接口,形成三个核心角色:

  1. 抽象组件(Component)
    • 定义叶子节点和容器节点的统一接口(如FileSystemComponentshow()add()remove());
    • 声明所有组件共有的方法(如展示、添加、删除),并可为默认方法提供实现(如叶子节点不支持add,默认抛出异常);
    • 是客户端与组件交互的唯一入口,保证客户端对所有组件的使用一致性。
  2. 叶子节点(Leaf)
    • 实现Component接口,代表树形结构中的最小单元(如File),不包含子组件;
    • 对 “添加 / 删除子组件” 等方法提供默认实现(通常抛出异常,因为叶子不能包含子节点);
    • 专注于自身的业务逻辑(如Fileshow()只展示文件名)。
  3. 容器节点(Composite)
    • 实现Component接口,代表树形结构中的组合单元(如Folder),可以包含子组件(叶子或其他容器);
    • 维护一个子组件集合(如List<FileSystemComponent>),并实现add()remove()等管理子组件的方法;
    • 对核心业务方法(如show())的实现需递归调用所有子组件的对应方法,从而实现 “整体操作”。

六、组合模式的两种形式

根据抽象组件是否声明 “管理子组件” 的方法(add/remove),组合模式分为两种实现形式:

1. 透明模式(Transparent)
  • 特点:抽象组件(Component)中声明所有方法(包括add/remove等管理子组件的方法),叶子节点和容器节点都实现这些方法(叶子节点对管理方法提供默认实现,如抛出异常);
  • 优点:客户端完全不区分叶子和容器,接口统一,符合 “里氏替换原则”(任何组件都可被替换);
  • 缺点:叶子节点实现了无意义的管理方法(如Fileadd方法),可能导致客户端误用(调用叶子的add方法会抛异常)。

上述文件系统示例即为透明模式

2. 安全模式(Safe)
  • 特点:抽象组件(Component)只声明业务方法(如show()),不包含add/remove等管理方法;管理方法仅在容器节点(Composite)中声明和实现;
  • 优点:叶子节点不会有多余的管理方法,避免客户端误用;
  • 缺点:客户端需要区分叶子和容器(调用add方法前需判断是否为容器),破坏了接口的统一性。

安全模式的文件系统示例

// 抽象组件:只声明业务方法
interface FileSystemComponent {void show(); 
}// 叶子节点:只实现业务方法
class File implements FileSystemComponent {@Override public void show() { ... }
}// 容器节点:额外声明管理方法
class Folder implements FileSystemComponent {@Override public void show() { ... }public void add(FileSystemComponent component) { ... } // 仅容器有public void remove(FileSystemComponent component) { ... } // 仅容器有
}// 客户端:需区分类型才能调用add(安全但不透明)
Folder root = new Folder("我的文档");
root.add(new File("笔记.txt")); // 正确(容器有add)
// file1.add(...) 编译报错(叶子没有add,安全)

七、总结

组合模式的核心是 “统一部分与整体,用树形结构管理层次关系”,通过抽象组件接口将叶子节点和容器节点统一起来,使客户端能以相同的方式操作单个对象和整个组合对象。

实际开发中,透明模式和安全模式的选择需权衡 “接口统一性” 和 “使用安全性”:若客户端完全信任且不会误用,透明模式更简洁;若需严格避免错误调用,安全模式更可靠。

记住:组合模式让树形结构的操作 “一视同仁”,让复杂层次的管理 “化繁为简”

2.4 装饰器模式(Decorator Pattern)

一、什么是装饰器模式?

装饰器模式是 ** 结构型模式中专注于 “动态增强对象功能”** 的核心模式,其核心思想是:在不改变原有对象结构和接口的前提下,通过 “包装”(装饰)的方式给对象动态添加新功能,且多个功能可以灵活组合

简单说:“像叠汉堡一样,在原有对象外面一层层包裹新功能,每层都保留原有的接口,最终得到一个增强版的对象”

日常生活中,给手机贴钢化膜(增强防刮功能)、套手机壳(增强防摔功能)就是装饰器模式的体现 —— 手机本身的功能没变,但通过 “装饰” 获得了新功能,且可以先贴膜再套壳(功能组合)。

二、为什么需要装饰器模式?(作用)

当需要给对象添加功能时,若用继承实现,会导致 “类爆炸”(每增加一个功能就需新增一个子类,多功能组合时子类数量呈指数增长)。装饰器模式的核心作用是:

  1. 动态扩展功能:在运行时为对象添加功能,而非编译时通过继承固定,灵活度更高。
  2. 功能组合灵活:多个装饰器可按任意顺序组合(如先加奶再加糖,或先加糖再加奶),实现多种功能组合效果。
  3. 不修改原有代码:通过 “包装” 而非修改原有类实现增强,符合 “开闭原则”(对扩展开放,对修改关闭)。
  4. 避免继承臃肿:用 “组合” 替代 “继承”,减少子类数量(从2^N减少为N个装饰器,N为功能数)。

三、反例:用继承实现功能扩展的问题

假设我们要设计一个咖啡系统,基础咖啡(如浓缩咖啡、拿铁)可以添加多种配料(如牛奶、糖、巧克力),每种配料会增加价格和描述。

用继承实现的缺陷:

// 1. 基础咖啡类
class Coffee {public String getDescription() { return "基础咖啡"; }public double getPrice() { return 10.0; }
}// 2. 具体基础咖啡
class Espresso extends Coffee {@Override public String getDescription() { return "浓缩咖啡"; }@Override public double getPrice() { return 15.0; }
}class Latte extends Coffee {@Override public String getDescription() { return "拿铁"; }@Override public double getPrice() { return 20.0; }
}// 3. 继承实现功能扩展(问题核心:每加一种配料就需新增子类)
// 浓缩咖啡+牛奶
class EspressoWithMilk extends Espresso {@Override public String getDescription() { return super.getDescription() + "+牛奶"; }@Override public double getPrice() { return super.getPrice() + 3.0; }
}// 浓缩咖啡+糖
class EspressoWithSugar extends Espresso {@Override public String getDescription() { return super.getDescription() + "+糖"; }@Override public double getPrice() { return super.getPrice() + 1.0; }
}// 浓缩咖啡+牛奶+糖(组合功能需新增子类)
class EspressoWithMilkAndSugar extends EspressoWithMilk {@Override public String getDescription() { return super.getDescription() + "+糖"; }@Override public double getPrice() { return super.getPrice() + 1.0; }
}// 拿铁+牛奶(虽然不合理,但继承体系下必须定义)
class LatteWithMilk extends Latte { ... }// 拿铁+糖+巧克力...(子类数量爆炸)

问题分析:

  • 类爆炸:若有M种基础咖啡和N种配料,需要M×(2^N - 1)个子类(如 2 种咖啡 ×3 种配料 = 14 个子类),系统极度臃肿;
  • 扩展困难:新增一种配料(如 “巧克力”),需为每种基础咖啡及所有现有组合新增子类(如EspressoWithChocolateEspressoWithMilkAndChocolate...),违反 “开闭原则”;
  • 功能组合固定:继承是编译时确定的,无法在运行时动态组合功能(如用户点单时临时选择 “加奶 + 加糖”,无法用继承实现);
  • 代码冗余:每种配料的价格和描述逻辑在多个子类中重复(如 “加牛奶” 的+3.0价格在EspressoWithMilkLatteWithMilk中重复)。

四、正例:用装饰器模式解决问题

核心改进:定义 “装饰器” 类,通过 “包装” 原有对象(咖啡)的方式添加功能(配料),装饰器与被装饰对象实现同一接口,支持多层嵌套组合

装饰器模式的实现:

// 1. 抽象组件(Component):定义被装饰对象和装饰器的统一接口
interface Coffee {String getDescription(); // 描述(如“浓缩咖啡+牛奶”)double getPrice();       // 价格(基础价+配料价)
}// 2. 具体组件(Concrete Component):基础对象(被装饰的核心)
class Espresso implements Coffee {@Overridepublic String getDescription() {return "浓缩咖啡";}@Overridepublic double getPrice() {return 15.0;}
}class Latte implements Coffee {@Overridepublic String getDescription() {return "拿铁";}@Overridepublic double getPrice() {return 20.0;}
}// 3. 抽象装饰器(Decorator):持有被装饰对象,实现统一接口(核心)
abstract class CoffeeDecorator implements Coffee {// 持有被装饰的Coffee对象(可以是基础咖啡,也可以是其他装饰器)protected Coffee decoratedCoffee;// 构造函数:传入被装饰对象public CoffeeDecorator(Coffee decoratedCoffee) {this.decoratedCoffee = decoratedCoffee;}// 默认实现:委托给被装饰对象(具体装饰器可重写增强)@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double getPrice() {return decoratedCoffee.getPrice();}
}// 4. 具体装饰器(Concrete Decorator):实现具体功能扩展
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}// 增强描述:添加“+牛奶”@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + "+牛奶";}// 增强价格:加3元@Overridepublic double getPrice() {return decoratedCoffee.getPrice() + 3.0;}
}class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + "+糖";}@Overridepublic double getPrice() {return decoratedCoffee.getPrice() + 1.0;}
}// 新增装饰器:巧克力(无需修改原有类)
class ChocolateDecorator extends CoffeeDecorator {public ChocolateDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic String getDescription() {return decoratedCoffee.getDescription() + "+巧克力";}@Overridepublic double getPrice() {return decoratedCoffee.getPrice() + 5.0;}
}// 5. 客户端:动态组合装饰器,实现功能扩展
public class Client {public static void main(String[] args) {// 基础咖啡:浓缩咖啡Coffee espresso = new Espresso();System.out.println(espresso.getDescription() + ",价格:" + espresso.getPrice()); // 输出:浓缩咖啡,价格:15.0// 装饰1:浓缩咖啡+牛奶(用MilkDecorator包装)Coffee espressoWithMilk = new MilkDecorator(espresso);System.out.println(espressoWithMilk.getDescription() + ",价格:" + espressoWithMilk.getPrice()); // 输出:浓缩咖啡+牛奶,价格:18.0// 装饰2:浓缩咖啡+牛奶+糖(再用SugarDecorator包装)Coffee espressoWithMilkAndSugar = new SugarDecorator(espressoWithMilk);System.out.println(espressoWithMilkAndSugar.getDescription() + ",价格:" + espressoWithMilkAndSugar.getPrice()); // 输出:浓缩咖啡+牛奶+糖,价格:19.0// 装饰3:拿铁+巧克力+糖(动态组合新功能)Coffee latteWithChocolateAndSugar = new SugarDecorator(new ChocolateDecorator(new Latte()));System.out.println(latteWithChocolateAndSugar.getDescription() + ",价格:" + latteWithChocolateAndSugar.getPrice()); // 输出:拿铁+巧克力+糖,价格:26.0}
}

改进效果:

  1. 解决类爆炸:类数量从M×(2^N - 1)减少为M + N(2 种基础咖啡 + 3 种配料 = 5 个类),扩展后优势更明显(5 种咖啡 + 10 种配料 = 15 个类,而非 5×1023=5115 个);
  2. 动态功能组合:客户端可在运行时按任意顺序组合装饰器(如 “牛奶 + 糖”“糖 + 牛奶”“巧克力 + 糖”),无需提前定义所有组合,灵活性极大提升;
  3. 符合开闭原则:
    • 新增基础咖啡(如 “美式咖啡”):只需实现Coffee接口,不影响现有代码;
    • 新增配料(如 “巧克力”):只需新增ChocolateDecorator,无需修改原有装饰器或基础咖啡;
  4. 功能复用:每种配料的逻辑(如牛奶 + 3 元)集中在一个装饰器中,避免代码重复(如MilkDecorator可同时装饰EspressoLatte);
  5. 不改变原有对象:基础咖啡(Espresso)的代码未被修改,装饰器通过 “包装” 而非修改实现增强,安全性高。

五、装饰器模式的核心结构

装饰器模式通过 “接口统一” 和 “对象包装” 实现功能扩展,包含 4 个核心角色:

  1. 抽象组件(Component)
    • 定义被装饰对象和装饰器的统一接口(如CoffeegetDescription()getPrice());
    • 是客户端与所有组件(基础对象和装饰器)交互的唯一入口,保证 “装饰器可替代被装饰对象”(里氏替换原则)。
  2. 具体组件(Concrete Component)
    • 实现Component接口,是被装饰的核心对象(如EspressoLatte);
    • 提供基础功能,是装饰器的 “包装起点”。
  3. 抽象装饰器(Decorator)
    • 实现Component接口,持有一个Component类型的引用(被装饰对象,如decoratedCoffee);
    • 作为所有具体装饰器的父类,默认实现Component的方法(委托给被装饰对象),为具体装饰器提供扩展基础。
  4. 具体装饰器(Concrete Decorator)
    • 继承Decorator实现具体的功能增强(如MilkDecorator添加牛奶的描述和价格);
    • 重写Component的方法,在调用被装饰对象方法的前后添加自身逻辑(如先获取被装饰对象的价格,再加 3 元)。

六、总结

装饰器模式的核心是 “动态包装,叠加增强”,通过将功能封装在装饰器中,以组合的方式给对象添加功能,解决了继承导致的类爆炸问题,同时保证了扩展的灵活性。

它的关键是抽象组件接口的设计(保证装饰器与被装饰对象的一致性)和装饰器的嵌套组合(实现功能叠加)。实际开发中,当需要灵活扩展对象功能且避免继承臃肿时,装饰器模式是最优解之一。

记住:装饰器模式让功能扩展 “按需组合”,让代码设计 “拥抱变化”

2.5 适配器模式(Adapter Pattern)

一、什么是适配器模式?

适配器模式是结构型模式中专门解决 “接口不兼容” 问题的核心模式,其核心思想是:将一个类的接口转换成客户端期望的另一种接口,使原本因接口不匹配而无法一起工作的类能够协同工作

简单说:“适配器就像‘转接头’,比如手机充电时的电源适配器,将 220V 交流电转换成手机需要的 5V 直流电,让不兼容的设备能正常工作”

日常生活中,HDMI 转 VGA 的转换器(让 HDMI 设备连接 VGA 显示器)、USB-C 转 USB-A 的转接头(让新设备使用旧 U 盘),都是适配器模式的典型体现。

二、为什么需要适配器模式?(作用)

在系统开发或维护中,经常会遇到 “现有类的接口与客户端需求不匹配” 的问题(如旧系统接口与新系统不兼容、第三方库接口与自定义接口不一致)。直接修改现有类或客户端代码可能成本高、风险大(如旧系统代码不能动)。适配器模式的核心作用是:

  1. 解决接口不兼容:让接口不同的类可以一起工作,无需修改原有代码(保护旧系统或第三方库)。
  2. 复用现有功能:在不改变现有类的前提下,通过适配器复用其功能,避免重复开发。
  3. 隔离变化:客户端只依赖目标接口,适配器隔离了现有类与客户端的直接交互,降低耦合。
  4. 平滑过渡:在系统升级时,用适配器兼容新旧接口,实现平滑过渡(如逐步替换旧系统)。

三、反例:接口不兼容的问题

假设我们正在开发一个新的电商支付系统,需要集成一个第三方的 “旧版支付网关”(OldPaymentGateway),但两者的接口不匹配:

  • 新系统期望的支付接口是PaymentProcessor,方法为processPayment(int amount)(金额单位为 “分”,整数类型);
  • 旧支付网关的接口是OldPaymentGateway,方法为pay(double money)(金额单位为 “元”,小数类型)。

不使用适配器模式的问题:

// 1. 新系统期望的目标接口(金额单位:分,整数)
interface PaymentProcessor {void processPayment(int amount); // 例如:1000 表示 10元
}// 2. 第三方旧支付网关(接口不兼容:金额单位为元,小数)
class OldPaymentGateway {// 例如:10.0 表示 10元public void pay(double money) {System.out.println("旧支付网关扣款:" + money + "元");}
}// 3. 客户端:尝试直接使用旧支付网关(编译错误)
public class Client {public static void main(String[] args) {// 新系统需要调用PaymentProcessor接口PaymentProcessor processor;// 问题:OldPaymentGateway没有processPayment(int)方法,无法直接赋值processor = new OldPaymentGateway(); // 编译报错:类型不兼容// 若强行修改客户端代码适配旧接口,会导致客户端与旧系统强耦合:OldPaymentGateway oldGateway = new OldPaymentGateway();int newAmount = 1000; // 新系统的10元(1000分)double oldAmount = newAmount / 100.0; // 手动转换为10.0元oldGateway.pay(oldAmount); // 调用旧方法// 缺点:若有100处调用,需写100次转换逻辑,代码冗余且耦合高}
}

问题分析:

  • 接口不兼容:旧系统的pay(double)与新系统的processPayment(int)接口不匹配(方法名、参数类型、单位都不同),无法直接集成;
  • 代码冗余:客户端需手动处理参数转换(分转元、整数转小数),若多处调用,转换逻辑重复,维护成本高;
  • 耦合度高:客户端直接依赖旧系统的接口(OldPaymentGateway),若旧系统升级(如方法名变更),所有客户端调用处都需修改;
  • 风险大:若旧系统是第三方库或遗留系统,修改其代码可能引入未知风险(如破坏其他依赖它的模块)。

四、正例:用适配器模式解决问题

核心改进:创建 “适配器类”,实现新系统的目标接口(PaymentProcessor),内部持有旧系统对象(OldPaymentGateway),在适配器中完成接口转换(参数类型、单位转换),使客户端通过适配器间接调用旧系统

适配器模式的实现(对象适配器)
// 1. 目标接口(新系统期望的接口)
interface PaymentProcessor {void processPayment(int amount); // 金额单位:分(整数)
}// 2. 适配者(Adaptee):需要被适配的旧系统/第三方类
class OldPaymentGateway {public void pay(double money) { // 金额单位:元(小数)System.out.println("旧支付网关扣款:" + money + "元");}
}// 3. 适配器(Adapter):实现目标接口,持有适配者引用,完成转换
class PaymentAdapter implements PaymentProcessor {// 持有旧系统对象(通过组合实现适配,对象适配器的核心)private OldPaymentGateway oldGateway;// 构造函数:传入旧系统对象public PaymentAdapter(OldPaymentGateway oldGateway) {this.oldGateway = oldGateway;}// 实现目标接口方法:在内部完成转换并调用旧系统方法@Overridepublic void processPayment(int amount) {// 1. 转换参数:分 -> 元(整数 -> 小数)double money = amount / 100.0;// 2. 调用旧系统的方法oldGateway.pay(money);}
}// 4. 客户端:只依赖目标接口,不直接接触旧系统
public class Client {public static void main(String[] args) {// 创建旧系统对象OldPaymentGateway oldGateway = new OldPaymentGateway();// 创建适配器(包装旧系统对象)PaymentProcessor processor = new PaymentAdapter(oldGateway);// 客户端调用目标接口,适配内部自动处理转换processor.processPayment(1000); // 新系统传入1000分(10元)// 输出:旧支付网关扣款:10.0元processor.processPayment(2500); // 25元(2500分)// 输出:旧支付网关扣款:25.0元}
}

改进效果:

  1. 解决接口不兼容:适配器PaymentAdapter实现了PaymentProcessor接口,同时内部调用OldPaymentGateway的方法,让新旧系统能够协同工作;
  2. 隔离客户端与旧系统:客户端只依赖PaymentProcessor接口,不知道OldPaymentGateway的存在,若旧系统升级,只需修改适配器,客户端代码无需变动;
  3. 消除代码冗余:参数转换逻辑(分转元)集中在适配器中,避免客户端多处重复编写;
  4. 复用旧系统功能:在不修改OldPaymentGateway代码的前提下,复用其支付功能,保护了旧系统的稳定性;
  5. 符合开闭原则:新增其他旧支付网关(如VeryOldPaymentGateway),只需新增对应的适配器(VeryOldPaymentAdapter),无需修改客户端或目标接口。

五、适配器模式的核心结构

适配器模式通过 “转换接口” 连接不兼容的类,包含 3 个核心角色:

  1. 目标接口(Target)
    • 客户端期望的标准接口(如PaymentProcessor),定义了客户端需要的方法(如processPayment);
    • 客户端只与目标接口交互,不关心具体实现。
  2. 适配者(Adaptee)
    • 已存在的接口不兼容的类或对象(如OldPaymentGateway),包含客户端需要的功能,但接口不符合目标要求;
    • 通常是旧系统、第三方库或不可修改的类。
  3. 适配器(Adapter)
    • 实现目标接口,并持有适配者的引用(对象适配器)或继承适配者(类适配器);
    • 核心职责是:将目标接口的方法调用 “转换” 为对适配者的方法调用(如参数转换、方法名映射)。

六、适配器模式的两种实现方式

根据适配器与适配者的关系(组合 vs 继承),适配器模式分为两种形式:

1. 对象适配器(推荐)
  • 实现方式:适配器通过组合(持有适配者对象的引用)实现适配(如上述PaymentAdapter持有OldPaymentGateway对象);
  • 优点:
    • 符合 “组合优于继承” 原则,灵活性更高(一个适配器可适配多个适配者);
    • 适配者无需是具体类(可以是接口),适用范围更广;
    • 不会因适配者的修改影响适配器(松耦合);
  • 缺点:需要为每个适配者创建对应的适配器(若适配者多,可能增加类数量)。
2. 类适配器(通过继承)
  • 实现方式:适配器通过继承适配者,并实现目标接口(Java 中需用多重继承,因此适配者必须是类,不能是接口);
// 类适配器:继承适配者,实现目标接口
class PaymentClassAdapter extends OldPaymentGateway implements PaymentProcessor {@Overridepublic void processPayment(int amount) {double money = amount / 100.0;super.pay(money); // 调用父类(适配者)的方法}
}// 客户端使用:
PaymentProcessor processor = new PaymentClassAdapter();
processor.processPayment(1000); // 同样生效
  • 优点:代码更简洁(无需持有适配者引用);
  • 缺点:
    • 适配者必须是类(不能是接口),限制了适用场景;
    • 适配器与适配者强耦合(适配者修改可能影响适配器);
    • 无法适配多个适配者(Java 不支持多重继承)。

实际开发中,对象适配器因灵活性更高,应用更广泛

七、总结

适配器模式的核心是 “接口转换,兼容共存”,通过引入适配器类,在不修改原有代码的前提下,解决了接口不兼容的问题,实现了新旧系统、第三方库与自定义代码的平滑集成。

它的关键是 “组合优于继承”(优先使用对象适配器)和 “隔离变化”(客户端只依赖目标接口)。实际开发中,当遇到 “接口不匹配但需要复用现有功能” 的场景时,适配器模式是最直接有效的解决方案。

记住:适配器模式是系统集成的 “万能转接头”,让不兼容的接口 “握手言和”

2.6 享元模式(Flyweight Pattern)

一、什么是享元模式?

享元模式是 ** 结构型模式中专注于 “复用细粒度对象、减少内存消耗”** 的核心模式,其核心思想是:通过共享技术,复用系统中大量相似的细粒度对象,只保留对象的 “内部状态”(可共享部分),而将 “外部状态”(不可共享部分)通过参数传递,从而减少对象实例的数量,降低内存开销

简单说:“把多个相同对象的‘共性部分’抽出来共享,‘个性部分’单独传入,避免重复创建相同对象浪费内存”

日常生活中,围棋 / 象棋的棋子是典型案例:围棋只有黑白两色,无需为每个棋子创建独立对象,只需共享 “黑色棋子” 和 “白色棋子” 两个实例,通过记录它们的位置(外部状态)即可表示整个棋盘的棋子分布,大幅减少对象数量。

二、为什么需要享元模式?(作用)

当系统中存在大量细粒度、高相似性的对象(如文档中的字符、游戏中的粒子、棋盘上的棋子)时,每个对象都独立创建会导致内存占用过高、系统性能下降。享元模式的核心作用是:

  1. 减少内存消耗:复用相似对象,将对象数量从 “海量” 减少到 “少量可共享实例”(如围棋从 361 个对象减少到 2 个),大幅降低内存占用。
  2. 提高对象复用率:避免重复创建结构相似的对象,减少对象初始化的性能开销(如避免重复加载相同的资源)。
  3. 分离可变与不可变状态:将对象的 “不变部分”(内部状态)共享,“可变部分”(外部状态)动态传入,使对象更灵活。
  4. 优化资源密集型场景:在创建对象成本高(如加载图片、初始化配置)的场景中,复用对象可显著提升系统响应速度。

三、反例:大量相似对象导致的内存浪费

假设我们要设计一个围棋游戏,棋盘上有 361 个交叉点,每个棋子包含 “颜色”(黑 / 白)和 “位置”(x,y 坐标)两个属性。

不使用享元模式的实现:

// 围棋棋子类(未使用享元)
class GoChess {private String color; // 棋子颜色(黑/白,本可共享)private int x;        // 位置x(不可共享,每个棋子不同)private int y;        // 位置y(不可共享)// 每个棋子都创建独立对象,包含颜色和位置public GoChess(String color, int x, int y) {this.color = color;this.x = x;this.y = y;System.out.println("创建棋子:" + color + ",位置(" + x + "," + y + ")");}public void display() {System.out.println("显示棋子:" + color + ",位置(" + x + "," + y + ")");}
}// 客户端:创建棋盘上的棋子(问题核心)
public class Client {public static void main(String[] args) {// 模拟创建10个黑棋和10个白棋(实际棋盘需361个)for (int i = 0; i < 10; i++) {// 每个黑棋都创建独立对象,颜色重复存储new GoChess("黑色", i, i).display();// 每个白棋都创建独立对象,颜色重复存储new GoChess("白色", i, 10 - i).display();}}
}

问题分析:

  • 内存浪费严重:361 个棋子中,只有 “黑色” 和 “白色” 两种类型,但每个棋子都独立存储 “颜色” 属性,导致相同颜色的信息重复存储(361 个对象的颜色字段重复,内存占用翻倍);
  • 对象创建成本高:若棋子还包含其他共享资源(如棋子图片、材质),每个对象都需重复加载这些资源,进一步消耗内存和 CPU;
  • 扩展性差:若需修改棋子颜色(如新增 “灰色” 棋子),需修改所有棋子对象的逻辑,维护成本高;
  • 系统性能下降:大量对象的创建和销毁会增加 JVM 的垃圾回收压力,导致系统响应变慢。

四、正例:用享元模式解决问题

核心改进:分离 “内部状态”(可共享的颜色)和 “外部状态”(不可共享的位置),创建 “享元工厂” 管理共享的棋子实例,客户端通过工厂获取共享实例,并传入外部状态(位置)使用。 享元模式的实现:

import java.util.HashMap;
import java.util.Map;// 1. 抽象享元(Flyweight):定义棋子的核心行为,包含外部状态的传入方法
interface ChessFlyweight {// 显示棋子,位置(x,y)为外部状态,通过参数传入void display(int x, int y);
}// 2. 具体享元(Concrete Flyweight):实现抽象享元,存储内部状态(颜色)
class ConcreteChess implements ChessFlyweight {// 内部状态:颜色(可共享,黑/白)private String color;// 构造函数:初始化内部状态(颜色)public ConcreteChess(String color) {this.color = color;System.out.println("创建享元棋子:" + color + "(仅创建一次)");}// 实现display方法:结合外部状态(位置)展示棋子@Overridepublic void display(int x, int y) {System.out.println("显示享元棋子:" + color + ",位置(" + x + "," + y + ")");}
}// 3. 享元工厂(Flyweight Factory):管理共享的享元实例,确保复用
class ChessFlyweightFactory {// 缓存享元实例(key:内部状态(颜色),value:享元对象)private static Map<String, ChessFlyweight> chessMap = new HashMap<>();// 获取享元实例:存在则返回,不存在则创建public static ChessFlyweight getChess(String color) {// 检查缓存中是否已有该颜色的棋子if (chessMap.containsKey(color)) {return chessMap.get(color);} else {// 创建新的享元实例并缓存ChessFlyweight chess = new ConcreteChess(color);chessMap.put(color, chess);return chess;}}
}// 4. 客户端:通过工厂获取享元实例,传入外部状态使用
public class Client {public static void main(String[] args) {// 模拟创建10个黑棋和10个白棋(实际棋盘需361个)for (int i = 0; i < 10; i++) {// 获取黑棋享元实例(仅第一次创建,后续复用)ChessFlyweight blackChess = ChessFlyweightFactory.getChess("黑色");blackChess.display(i, i); // 传入外部状态(位置)// 获取白棋享元实例(仅第一次创建,后续复用)ChessFlyweight whiteChess = ChessFlyweightFactory.getChess("白色");whiteChess.display(i, 10 - i); // 传入外部状态(位置)}}
}

改进效果:

  1. 大幅减少内存消耗:无论创建多少棋子,系统中只存在 “黑色” 和 “白色” 两个享元实例(361 个棋子场景下,对象数量从 361 个减少到 2 个),避免重复存储内部状态;
  2. 复用对象资源:享元实例仅创建一次,若棋子包含图片等资源,只需加载一次,降低资源加载成本;
  3. 分离可变与不可变状态:内部状态(颜色)固定共享,外部状态(位置)动态传入,客户端可灵活设置棋子位置,不影响享元实例的复用;
  4. 易于扩展:新增 “灰色” 棋子时,只需通过工厂获取ChessFlyweightFactory.getChess("灰色"),自动创建并缓存新的享元实例,客户端无需修改核心逻辑;
  5. 提升系统性能:减少对象创建和垃圾回收的压力,系统响应速度显著提升。

五、享元模式的核心结构

享元模式通过 “分离状态 + 缓存复用” 实现对象优化,包含 4 个核心角色:

  1. 抽象享元(Flyweight)
    • 定义享元对象的核心行为接口(如ChessFlyweightdisplay方法);
    • 声明外部状态的传入方式(如display方法的x,y参数),明确内部状态与外部状态的分离边界。
  2. 具体享元(Concrete Flyweight)
    • 实现Flyweight接口,存储可共享的内部状态(如ConcreteChesscolor字段);
    • 内部状态独立于环境,不随外部变化(如黑色棋子的颜色始终为黑色);
    • 核心方法结合外部状态完成业务逻辑(如display方法结合位置展示棋子)。
  3. 享元工厂(Flyweight Factory)
    • 负责创建和管理享元实例,通过缓存机制确保相同内部状态的享元只存在一个;
    • 提供get方法(如getChess),根据内部状态从缓存中获取或创建享元实例,是享元模式的核心管理组件。
  4. 外部状态(Extrinsic State)
    • 不可共享的状态,随环境变化而变化(如棋子的x,y位置);
    • 由客户端传入享元对象的方法中,不存储在享元实例内部,避免影响享元的复用。
关键:内部状态 vs 外部状态
  • 内部状态:可共享、不变的属性(如棋子颜色、字符编码、图片资源),存储在享元实例中;

  • 外部状态:不可共享、可变的属性(如位置、时间、用户信息),由客户端传入,不存储在享元中。

    两者的分离是享元模式的核心,决定了对象复用的效率。

六、享元模式的两种形式

根据是否包含外部状态,享元模式分为 “单纯享元模式” 和 “复合享元模式”:

1. 单纯享元模式(如上述示例)
  • 特点:所有享元实例都是 “单纯享元”,仅包含内部状态,外部状态通过方法参数传入;
  • 适用场景:外部状态简单,且与享元实例的关联松散(如棋子位置与棋子本身无强绑定)。
2. 复合享元模式(组合享元)
  • 特点:将多个单纯享元组合成一个 “复合享元”,复合享元本身也可作为享元被缓存;
  • 适用场景:外部状态存在组合关系(如文档中的 “单词” 由多个 “字符” 享元组成,单词可作为复合享元缓存)。

实际开发中,单纯享元模式应用更广泛,复合享元模式因结构复杂,仅在特定场景(如组合对象的复用)中使用。

七、总结

享元模式的核心是 “共享复用细粒度对象,分离内部与外部状态”,通过享元工厂的缓存机制,将对象数量从 “海量” 压缩到 “少量可共享实例”,从而解决内存浪费和性能下降的问题。

它的关键是准确区分 “内部状态”(可共享)和 “外部状态”(不可共享),并通过工厂管理享元实例的创建和复用。实际开发中,当遇到 “大量相似对象导致内存紧张” 的场景时,享元模式是最优解之一。

记住:享元模式让细粒度对象的复用 “化繁为简”,让系统内存的使用 “精打细算”

2.7 外观模式(Facade Pattern)教程

一、什么是外观模式?

外观模式是 结构型模式中专注于 简化复杂系统接口的核心模式,其核心思想是:为一组复杂的子系统提供一个统一的高层接口,客户端通过这个接口与子系统交互,而无需关心子系统的内部细节,从而降低客户端与子系统的耦合度

简单说:“外观就像‘总开关’,比如家庭影院的遥控器,按一个‘观影模式’按钮,自动打开电视、音响、蓝光播放器并调整好设置,无需手动操作每个设备”

日常生活中,电脑的 “开机键” 是外观模式的典型体现:按下开机键,电脑自动完成主板通电、CPU 启动、内存加载、硬盘自检等一系列子系统操作,用户无需关心内部细节;手机的 “飞行模式” 也是如此,一键关闭蓝牙、Wi-Fi、蜂窝网络等多个子系统。

二、为什么需要外观模式?(作用)

当系统包含多个子系统(如家庭影院的电视、音响、播放器),且子系统之间存在复杂交互时,客户端直接操作子系统会导致:

  • 客户端需要了解所有子系统的接口和交互逻辑,学习成本高;
  • 客户端与多个子系统强耦合,子系统的任何修改(如接口变更)都会影响客户端;
  • 代码冗余,相同的子系统交互逻辑在多个客户端中重复编写。

外观模式的核心作用是:

  1. 简化接口:为复杂子系统提供统一入口,客户端只需调用一个接口,无需操作多个子系统;
  2. 降低耦合:客户端与子系统解耦,子系统的内部变化不影响客户端(只需保证外观接口不变);
  3. 隔离复杂性:隐藏子系统的内部细节,客户端无需了解子系统的实现逻辑;
  4. 统一复用:将子系统的交互逻辑集中在外观类中,避免客户端重复编写相同代码。

三、反例:直接操作子系统的问题

假设我们要实现一个 “家庭影院” 系统,包含电视(TV)、音响(SoundSystem)、蓝光播放器(BlueRayPlayer)三个子系统,观看电影需要依次执行:打开电视→打开音响→打开播放器→切换电视输入源→调整音量→播放电影。

不使用外观模式的实现:

// 1. 子系统1:电视
class TV {public void turnOn() { System.out.println("电视已打开"); }public void turnOff() { System.out.println("电视已关闭"); }public void setInput(String input) { System.out.println("电视输入源切换为:" + input); }
}// 2. 子系统2:音响
class SoundSystem {public void turnOn() { System.out.println("音响已打开"); }public void turnOff() { System.out.println("音响已关闭"); }public void setVolume(int volume) { System.out.println("音响音量调整为:" + volume); }
}// 3. 子系统3:蓝光播放器
class BlueRayPlayer {public void turnOn() { System.out.println("蓝光播放器已打开"); }public void turnOff() { System.out.println("蓝光播放器已关闭"); }public void play() { System.out.println("蓝光播放器开始播放电影"); }
}// 客户端:直接操作所有子系统(问题核心)
public class Client {public static void main(String[] args) {// 1. 客户端需手动创建所有子系统对象TV tv = new TV();SoundSystem sound = new SoundSystem();BlueRayPlayer player = new BlueRayPlayer();// 2. 客户端需记住复杂的操作步骤和顺序tv.turnOn();sound.turnOn();player.turnOn();tv.setInput("HDMI"); // 必须切换到播放器的输入源sound.setVolume(20); // 必须调整音量player.play();// 问题:若要停止观影,还需手动关闭所有设备,步骤繁琐// 问题:若子系统变更(如新增投影仪),客户端代码需大幅修改}
}

问题分析:

  • 客户端负担重:客户端必须知道所有子系统的存在,掌握正确的操作顺序(如先开设备再切换输入源),一旦顺序错误(如先播放再开电视),系统无法正常工作;
  • 耦合度极高:客户端直接依赖TVSoundSystemBlueRayPlayer三个子系统,若任何一个子系统的方法名变更(如turnOn改为powerOn),客户端代码必须同步修改;
  • 代码复用性差:若有多个客户端(如手机 APP、遥控器)需要实现 “观影模式”,每个客户端都要重复编写相同的操作步骤,维护成本高;
  • 扩展困难:新增子系统(如投影仪、幕布)时,所有客户端都需修改代码以适配新设备,违反 “开闭原则”。

四、正例:用外观模式解决问题

核心改进:创建 “外观类”(如HomeTheaterFacade),封装子系统的所有交互逻辑,客户端只需调用外观类的简单接口(如watchMovie()),由外观类内部协调各个子系统

外观模式的实现:

// 1. 子系统(与反例相同,无需修改)
class TV {public void turnOn() { System.out.println("电视已打开"); }public void turnOff() { System.out.println("电视已关闭"); }public void setInput(String input) { System.out.println("电视输入源切换为:" + input); }
}class SoundSystem {public void turnOn() { System.out.println("音响已打开"); }public void turnOff() { System.out.println("音响已关闭"); }public void setVolume(int volume) { System.out.println("音响音量调整为:" + volume); }
}class BlueRayPlayer {public void turnOn() { System.out.println("蓝光播放器已打开"); }public void turnOff() { System.out.println("蓝光播放器已关闭"); }public void play() { System.out.println("蓝光播放器开始播放电影"); }
}// 2. 外观类(Facade):封装子系统交互,提供统一接口
class HomeTheaterFacade {// 持有子系统的引用(外观类知道所有子系统)private TV tv;private SoundSystem soundSystem;private BlueRayPlayer blueRayPlayer;// 构造函数:初始化子系统(也可通过依赖注入传入)public HomeTheaterFacade(TV tv, SoundSystem soundSystem, BlueRayPlayer blueRayPlayer) {this.tv = tv;this.soundSystem = soundSystem;this.blueRayPlayer = blueRayPlayer;}// 外观接口1:观影模式(封装所有启动步骤)public void watchMovie() {System.out.println("=== 准备观影 ===");tv.turnOn();soundSystem.turnOn();blueRayPlayer.turnOn();tv.setInput("HDMI");soundSystem.setVolume(20);blueRayPlayer.play();System.out.println("=== 观影开始 ===");}// 外观接口2:关闭所有设备(封装所有关闭步骤)public void endMovie() {System.out.println("=== 结束观影 ===");blueRayPlayer.turnOff();soundSystem.turnOff();tv.turnOff();System.out.println("=== 所有设备已关闭 ===");}
}// 3. 客户端:只与外观类交互,无需关心子系统
public class Client {public static void main(String[] args) {// 1. 创建子系统对象(可由工厂或容器注入)TV tv = new TV();SoundSystem sound = new SoundSystem();BlueRayPlayer player = new BlueRayPlayer();// 2. 创建外观类(将子系统交给外观管理)HomeTheaterFacade facade = new HomeTheaterFacade(tv, sound, player);// 3. 客户端只需调用外观接口,一步完成复杂操作facade.watchMovie(); // 输出:// === 准备观影 ===// 电视已打开// 音响已打开// 蓝光播放器已打开// 电视输入源切换为:HDMI// 音响音量调整为:20// 蓝光播放器开始播放电影// === 观影开始 ===// 结束观影facade.endMovie();// 输出:// === 结束观影 ===// 蓝光播放器已关闭// 音响已关闭// 电视已关闭// === 所有设备已关闭 ===}
}

改进效果:

  1. 客户端操作简化:客户端无需记住复杂的子系统操作步骤,只需调用watchMovie()endMovie()两个接口,学习成本和使用难度大幅降低;
  2. 解耦客户端与子系统:客户端只依赖HomeTheaterFacade,不直接访问TVSoundSystem等子系统,即使子系统的方法名或逻辑变更(如turnOn改为powerOn),只需修改外观类,客户端代码无需变动;
  3. 逻辑集中复用:子系统的交互逻辑(如先开电视再开音响)集中在外观类中,多个客户端(如手机 APP、遥控器)可共用同一套逻辑,避免重复代码;
  4. 易于扩展:新增子系统(如投影仪Projector)时,只需修改外观类(在watchMovie中添加投影仪的启动逻辑),客户端无需任何修改,符合 “开闭原则”;
  5. 隔离复杂性:客户端完全不知道子系统的内部实现(如电视如何切换输入源),只需关注 “观影” 这个最终目标,降低了系统的认知成本。

五、外观模式的核心结构

外观模式通过 “统一接口封装子系统” 实现简化,包含 3 个核心角色:

  1. 外观角色(Facade)
    • 是客户端交互的唯一入口(如HomeTheaterFacade);
    • 持有所有子系统的引用,知道每个子系统的职责;
    • 提供高层接口(如watchMovie()),内部协调多个子系统完成复杂操作,隐藏子系统的交互细节。
  2. 子系统角色(Subsystem)
    • 是系统中完成具体功能的模块(如TVSoundSystem);
    • 子系统之间可能存在交互,但它们不知道外观类的存在,也不依赖外观类;
    • 若没有外观类,子系统可直接被客户端调用(但会导致耦合问题)。
  3. 客户端(Client)
    • 通过外观角色的接口与系统交互,不直接访问子系统;
    • 只关心外观提供的高层功能(如 “观影”),不关心子系统的具体实现。

六、外观模式的工作原理

外观模式的核心是 “封装与委托”:

  1. 封装:外观类将子系统的复杂交互逻辑(如 “开机顺序”“参数设置”)封装在自身方法中,客户端无需感知;
  2. 委托:外观类的方法被调用时,内部会按顺序调用各个子系统的相关方法(如watchMovie()依次调用tv.turnOn()soundSystem.turnOn()等),完成委托执行。

这种机制保证了:

  • 客户端与子系统的解耦(客户端→外观→子系统,而非客户端→子系统);
  • 子系统的独立性(子系统可单独修改或复用,只要外观类适配即可);
  • 系统的易用性(复杂操作简化为一个接口调用)。

七、总结

外观模式的核心是 “封装复杂,提供简单”,通过引入外观类作为统一入口,将客户端与复杂子系统隔离开来,既简化了客户端的使用,又降低了系统的耦合度。

它的关键是合理设计外观类的接口(只暴露必要的高层功能),避免外观类职责过重(可通过多个外观类拆分不同功能模块)。实际开发中,当系统变得复杂难以使用时,外观模式往往是 “化繁为简” 的最佳选择。

记住:外观模式是复杂系统的 “简化器”,让客户端与系统的交互 “一步到位”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/944277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025 年工业 pH 计厂家最新推荐榜单:在线 / 污水 / 脱硫 / 发酵 / 化工 / 反应釜 / 防爆 / 纯水 / 双通道 pH 计优质品牌汇总,帮企业选靠谱设备

引言 当前工业生产与科研实验中,pH 计作为关键检测仪器,其性能直接影响生产质量与实验结果。但市场上 pH 计品牌繁杂,部分产品存在测量误差大、适配性差、操作复杂等问题,且不少品牌缺乏完善售后,仪器故障时难以及…

设计模式1-创建型模式

一 设计模式是什么 设计模式是软件工程中针对常见设计问题的、经过验证的、可复用的解决方案。它不是具体的代码实现,而是一套 “设计模板” 或 “思想指导”,用于解决软件设计中反复出现的问题(如 “如何灵活创建对…

[LangChain] 07. 消息占位

在做聊天应用时,我们的提示词往往是一串按角色分好的消息 [SystemMessage {"content": "xxx",},HumanMessage {"content": "xxx",}, 占位符1AIMessage {"content"…

2025 年洗碗机源头厂家最新推荐榜:聚焦实力企业,为餐饮及企事业单位选购提供权威参考通道式/链条式/流水线/酒店/学校/工厂/全自动洗碗机公司推荐

引言 当前,餐饮行业规模持续扩张,企事业单位后勤升级需求迫切,商用洗碗机作为提升餐具清洁效率、保障卫生安全的核心设备,市场需求日益旺盛。然而,市场上洗碗机源头厂家良莠不齐,部分厂家缺乏核心技术,设备洗净…

数据同步问题解析

1、不能全量同步 由于有时候数据量会非常的大,如果直接通过所有的数据会存在OOM的情况,所以应该使用分页查询,慢慢的去同步数据 2、游标查询 由于数据可能会存在增删改的问题,所以使用limit+offset会出现数据问题,…

微算法科技(NASDAQ MLGO)基于区块链点阵加密算法的物联网轻量级方案:构建物联网安全基石

物联网设备数量呈爆发式增长,安全问题愈发凸显。传统加密方案因计算资源与能耗需求高,难以适配资源受限的物联网设备。同时,物联网数据的隐私性、完整性和设备间信任机制面临挑战。微算法科技(NASDAQ MLGO)开发基…

Kubernetes 在企业级场景下的全流程落地实践 - 教程

Kubernetes 在企业级场景下的全流程落地实践 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

鸿蒙电脑,给世界多一种选择!

5 月 8 日,华为在深圳召开了鸿蒙电脑技术与生态沟通会,鸿蒙电脑首次亮相。5 月 19 日,华为在成都召开了 nova14 系列及鸿蒙电脑新品发布会,鸿蒙电脑正式发布。鸿蒙电脑,即搭载鸿蒙操作系统的电脑,目前发布了两款…

RFSOC学习记录(一)RF data converter总览

RFSOC介绍系列第一篇,总览最近使用了27DR的板子,是第一次接触RFSOC的产品,遇到了很多个奇怪的问题,写篇文章总结一下我对RF data converter这个ip核的看法 这是我遇到过配置最复杂的ip核,包括接口数量,时钟结构种…

git ssh 已配置公钥,但仍然报错: Permission denied (publickey) - lay

背景 在Raspberry Pi 上通过 git clone ssh://xxx@<host_name>:29418/<REPOSITORY_NAME.git> 克隆 Gerrit 仓库时失败,报错 Permission denied (publickey); 但使用 git@ 格式(如 git clone git@<ho…

2025 年最新外呼系统厂家最新推荐排行榜:深度解析技术实力、服务体系及行业适配方案解决方案 / 电话营销 / 智能 / 电销卡 / 平台搭建 / 电销卡 / 线路公司推荐

引言 在数字化转型全面深化的 2025 年,外呼系统已成为企业打通客户沟通链路、提升业务转化效率的核心工具。但当前市场中,外呼厂商资质良莠不齐,部分厂商缺乏合规资质、系统稳定性差、售后响应滞后,导致企业面临合…

加速智能体开发:从 Serverless 运行时到 Serverless AI 运行时

本文整理自 2025 云栖大会,阿里云智能集团产品专家,洪晓龙演讲议题《函数计算:AI 时代的最佳运行时》 在云计算与人工智能深度融合的背景下,Serverless 技术作为云原生架构的集大成者,正加速向AI原生架构演进。阿…

RFSOC学习记录(三)LMK04828时钟配置

rfsoc学习记录第三篇,lmk04828时钟介绍与配置上一篇讲了rf data converter这个ip核整体的时钟结构 以及rfdc的运行机制,在我们实际应用的过程中,还需要在PS端通过配置寄存器的方式启动时钟,而通过TICS PRO这个工具…

设计原则-教程

设计原则软件工程的七大设计原则包括开闭原则、接口隔离原则、里氏替换原则、合成复用原则、迪米特法则(最小知识原则)、单一职责原则、依赖倒置原则。(开口里合最单依) 一 开闭原则 开闭原则(Open Closed Princi…

Failed to start nginx.service: Unit nginx.service not found.

分享一篇nginx安装后,出现:Failed to start nginx.service: Unit nginx.service not found. 我们需要如何解决这个问题 在使用 Nginx 1.26.3 时遇到了 nginx.service找不到的问题。这是因为通过源码编译安装 Nginx 后…

WTAPI框架/微信个人号开发协议

WTAPI框架/微信个人号开发协议、个微协议/微信二次开发/ipad协议/WTAPI框架 WTAPI框架,是一个开发协议,专为开发微信机器人和自动化任务而设计。它允许开发者通过微信公众号接口实现各种功能,包括但不限于关键字回复…

AE/PR插件-Beauty Box v6.0.2 专业视频人像磨皮美颜润肤插件

插件简介 Beauty Box 是一款由Digital Anarchy 出品的专业视频人像磨皮美颜插件,可以智能识别人像肤色,一键磨皮。Beauty Box Video 4.0采用了最畅销,最受好评的皮肤平滑技术,并提供了实时渲染(在某些GPU上)。Be…

2025 年北京紧急 / 北京上门 / 北京防盗门 / 北京密码锁开锁公司推荐:北京锁王开锁有限公司 —— 安全锁具服务的可靠之选

行业背景 随着城市化进程加速与智能家居普及,北京开锁服务需求持续攀升,涵盖住宅、汽车、商铺等多元场景。但市场中仍混杂未经工商注册、公安备案的机构,存在技师资质不明、服务后加价、锁具损坏等乱象,甚至潜藏财…

2025 年封口机厂家推荐:武汉吕工机械,以技术创新驱动包装行业新发展

在当今包装设备行业,封口机作为关键设备,其性能和质量直接影响到产品的包装效果和生产效率。随着市场对包装需求的不断提升,各大封口机厂家纷纷加大研发投入,以满足客户日益多样化的需求。武汉吕工机械有限公司便是…

ubuntu 20.04 安装 maven 3.8.1

一、安装依赖:Java 环境 sudo apt update sudo apt install openjdk-11-jdk -y java -version二、下载 Maven 3.8.1 从 Apache 官网下载对应版本的压缩包: wget https://archive.apache.org/dist/maven/maven-3/3.8.…