设计模式之责任链模式
引言
在职场中,请假流程大家都再熟悉不过:申请 1 至 2 天的假期,只需直属主管审批即可;若要请假 3 至 5 天,就需部门负责人进行复核;而超过 5 天的假期申请,则必须由总经理最终定夺。要是遇到超长假期,甚至得上报至总裁或董事长那里。这种层层递进的审批机制,宛如一套设计精妙的权限传递系统:每位处理者只能在自己的职权范围内行使决策权,一旦请求超出了当前处理者的能力范围,便会依照既定的权力链条自动流转,直至抵达具备相应权限的决策节点。
这一场景,其实是软件设计模式里经典的责任链模式(Chain of Responsibility Pattern) 在现实世界中的生动呈现。责任链模式通过搭建动态的处理链路,让多个对象都有机会对请求进行处理。这种方式巧妙地实现了请求发起者与处理者之间的解耦,同时也赋予了系统处理逻辑出色的灵活性与扩展性。
概述
定义
责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行发送。当请求到达链中的某个处理者时,该处理者会决定是处理这个请求还是将其传递给链中的下一个处理者。这样就形成了一条责任链,请求在这条链上依次传递,直到有一个处理者能够处理它为止。
结构
- 抽象处理者(Handler):定义了一个处理请求的抽象方法,并持有一个指向下一个处理者的引用。它为具体处理者提供了统一的接口,使得所有处理者都具有相同的处理请求的方法签名。
- 具体处理者(Concrete Handler):实现了抽象处理者定义的处理请求的方法。在具体处理者中,会根据自身的逻辑来判断是否能够处理请求,如果能够处理,则进行处理;如果不能处理,则将请求传递给下一个处理者。
- 客户端(Client):负责创建责任链,并向链的起始端发送请求。客户端不需要知道具体是哪个处理者最终处理了请求,只需要将请求发送到责任链上即可。
工作原理
- 首先,客户端创建责任链,将各个具体处理者按照一定的顺序连接起来,形成一条链。
- 然后,客户端向链的起始端的处理者发送请求。
- 当请求到达某个具体处理者时,该处理者会检查自己是否能够处理这个请求。如果可以处理,就执行相应的处理逻辑,处理完请求后,请求的传递过程就结束了。如果不能处理,它就会将请求传递给链中的下一个处理者。
- 这个过程会一直持续,直到请求被某个处理者处理或者到达链的末端都没有被处理。如果到达链的末端仍未被处理,可以根据具体情况进行相应的处理,比如抛出异常或者返回一个默认结果。
示例
我们以引言中的例子编写示例代码
类图
C++实现
#include <iostream>
#include <string>
#include <memory>// 请假请求对象
class LeaveRequest {
public:int days;std::string name;LeaveRequest(int d, const std::string& n) : days(d), name(n) {}
};// 抽象处理者
class Handler {
protected:std::unique_ptr<Handler> successor; // 下一级处理者const std::string title; // 职位名称public:explicit Handler(const std::string& t) : title(t) {}virtual ~Handler() = default;// 设置下一级处理者void setSuccessor(std::unique_ptr<Handler> next) {successor = std::move(next);}// 处理请求的抽象方法virtual void processRequest(const LeaveRequest& req) = 0;protected:// 默认传递行为void delegateRequest(const LeaveRequest& req) {if (successor) {successor->processRequest(req);} else {std::cout << "⚠️ " << req.name << "的" << req.days<< "天假期申请需要更高权限审批" << std::endl;}}
};// 具体处理者:直属主管
class Supervisor : public Handler {
public:Supervisor() : Handler("直属主管") {}void processRequest(const LeaveRequest& req) override {if (req.days <= 2) {std::cout << "✅ [" << title << "] 批准" << req.name<< "的" << req.days << "天假期" << std::endl;} else {std::cout << "➡️ [" << title << "] 将" << req.name<< "的申请递交给上级" << std::endl;delegateRequest(req);}}
};// 具体处理者:部门负责人
class DepartmentManager : public Handler {
public:DepartmentManager() : Handler("部门负责人") {}void processRequest(const LeaveRequest& req) override {if (req.days <= 5) {std::cout << "✅ 【" << title << "】批准" << req.name<< "的" << req.days << "天假期" << std::endl;} else {std::cout << "➡️ 【" << title << "】将" << req.name<< "的申请递交给上级" << std::endl;delegateRequest(req);}}
};// 具体处理者:总经理
class GeneralManager : public Handler {
public:GeneralManager() : Handler("总经理") {}void processRequest(const LeaveRequest& req) override {if (req.days <= 15) {std::cout << "✅ 【★" << title << "★】批准" << req.name<< "的" << req.days << "天假期" << std::endl;} else {std::cout << "➡️ 【★" << title << "★】将" << req.name<< "的申请递交给上级" << std::endl;delegateRequest(req);}}
};// 具体处理者:董事长
class Chairman : public Handler {
public:Chairman() : Handler("董事长") {}void processRequest(const LeaveRequest& req) override {std::cout << "✅ 【最高决议】" << title << "批准" << req.name<< "的" << req.days << "天长假" << std::endl;}
};int main() {// 使用智能指针创建责任链auto bob = std::make_unique<Chairman>();auto lucy = std::make_unique<GeneralManager>();auto emma = std::make_unique<DepartmentManager>();auto john = std::make_unique<Supervisor>();// 构建责任链(逆序设置)lucy->setSuccessor(std::move(bob)); // 总经理持有董事长emma->setSuccessor(std::move(lucy)); // 部门经理持有总经理john->setSuccessor(std::move(emma)); // 主管持有部门经理// 测试用例LeaveRequest requests[] = {{1, "张三"},{3, "李四"},{7, "王五"},{20, "赵六"},{30, "陈七"}};for (const auto& req : requests) {std::cout << "\n====== 处理" << req.name<< "的" << req.days << "天假期申请 ======" << std::endl;john->processRequest(req);}return 0;
}
运行结果如下:
Java实现
import java.util.Objects;/*** 请假请求实体类(Immutable Object)*/
final class LeaveRequest {private final int days;private final String name;/*** 构造请假请求* @param days 请假天数(必须大于0)* @param name 申请人姓名(非空)*/public LeaveRequest(int days, String name) {if (days <= 0) throw new IllegalArgumentException("请假天数必须为正数");this.days = days;this.name = Objects.requireNonNull(name, "申请人姓名不能为空");}public int getDays() {return days;}public String getName() {return name;}
}/*** 抽象处理者(Handler)*/
abstract class Handler {protected Handler nextHandler;protected final String title;/*** 构造处理者* @param title 处理者职位名称*/protected Handler(String title) {this.title = title;}/*** 设置下一级处理者(构建责任链)* @param nextHandler 下一级处理者实例* @return 当前处理者(支持链式调用)*/public Handler setNext(Handler nextHandler) {this.nextHandler = nextHandler;return this;}/*** 处理请求的模板方法* @param request 请假请求*/public final void handleRequest(LeaveRequest request) {if (canHandle(request)) {approve(request);} else {delegate(request);}}/*** 判断当前处理者能否处理请求* @param request 请假请求* @return 是否具有处理权限*/protected abstract boolean canHandle(LeaveRequest request);/*** 审批通过的具体操作* @param request 请假请求*/protected void approve(LeaveRequest request) {System.out.printf("✅ [%s] 批准 %s 的 %d 天假期%n", title, request.getName(), request.getDays());}/*** 转交请求给下一级处理者* @param request 请假请求*/protected void delegate(LeaveRequest request) {System.out.printf("➡️ [%s] 将 %s 的申请递交给上级%n", title, request.getName());if (nextHandler != null) {nextHandler.handleRequest(request);} else {System.out.printf("⚠️ %s 的 %d 天假期申请需要更高权限审批%n",request.getName(), request.getDays());}}
}/*** 直属主管(Concrete Handler)* 处理规则:可批准1-2天的假期*/
class Supervisor extends Handler {public Supervisor() {super("直属主管");}@Overrideprotected boolean canHandle(LeaveRequest request) {return request.getDays() <= 2;}
}/*** 部门经理(Concrete Handler)* 处理规则:可批准3-5天的假期*/
class DepartmentManager extends Handler {public DepartmentManager() {super("部门经理");}@Overrideprotected boolean canHandle(LeaveRequest request) {return request.getDays() <= 5;}
}/*** 总经理(Concrete Handler)* 处理规则:可批准6-15天的假期*/
class GeneralManager extends Handler {public GeneralManager() {super("总经理");}@Overrideprotected boolean canHandle(LeaveRequest request) {return request.getDays() <= 15;}@Overrideprotected void approve(LeaveRequest request) {System.out.printf("✅ 【★%s★】批准 %s 的 %d 天长假%n", title, request.getName(), request.getDays());}
}/*** 董事长(Concrete Handler)* 处理规则:可批准任意天数的假期*/
class Chairman extends Handler {public Chairman() {super("董事长");}@Overrideprotected boolean canHandle(LeaveRequest request) {return true; // 最终处理节点}@Overrideprotected void approve(LeaveRequest request) {System.out.printf("✅ 【最高决议】%s 批准 %s 的 %d 天超长假期%n", title, request.getName(), request.getDays());}
}/*** 客户端使用示例*/
public class ChainOfResponsibilityDemo {public static void main(String[] args) {// 构建责任链Handler chain = new Supervisor().setNext(new DepartmentManager()).setNext(new GeneralManager()).setNext(new Chairman());// 创建测试请求LeaveRequest[] requests = {new LeaveRequest(1, "张三"),new LeaveRequest(3, "李四"),new LeaveRequest(7, "王五"),new LeaveRequest(20, "赵六"),new LeaveRequest(30, "陈七")};// 处理所有请求for (LeaveRequest request : requests) {System.out.printf("%n====== 处理 %s 的 %d 天假期申请 ======%n",request.getName(), request.getDays());chain.handleRequest(request);}}
}
代码说明:
- 责任链结构:
- Supervisor(直属主管):处理1-2天假期
- DepartmentManager(部门负责人):处理3-5天假期
- GeneralManager(总经理):处理6-15天假期
- Chairman(董事长):处理15天以上超长假期
- 运行特征:
- 每个处理者只处理权限范围内的请求
- 超出权限时自动传递到下一级
- 董事长作为最终处理节点
- 使用统一接口
processRequest
处理请求
责任链模式遵循的设计原则
单一职责原则(Single Responsibility Principle)
- 含义:一个类应该只有一个引起它变化的原因,即每个类只负责一项职责。
- 在责任链模式中的体现:在责任链模式里,每个具体处理者类只负责处理特定范围内的请求,只关注自身的处理逻辑。例如在公司请假审批的责任链中,直属主管只负责审批 1 - 2 天的请假申请,部门负责人只处理 3 - 5 天的请假申请,总经理处理 6 - 15 天的请假申请等。每个处理者的职责单一明确,当某个处理者的处理规则发生变化时,只需要修改该处理者类,不会影响到其他处理者。
开闭原则(Open - Closed Principle)
- 含义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。也就是说,在不修改现有代码的基础上,能够扩展新的功能。
- 在责任链模式中的体现:当需要添加新的处理者时,只需要创建一个新的具体处理者类,实现抽象处理者接口,并将其添加到责任链中即可,而不需要修改现有的处理者类和客户端代码。例如,在一个日志处理的责任链中,如果要增加一种新的日志过滤规则,只需要创建一个新的日志过滤器类并插入到合适的位置,原有的日志处理逻辑不受影响。
里氏替换原则(Liskov Substitution Principle)
- 含义:子类可以替换其父类并且出现在父类能够出现的任何地方,而不会影响系统的正确性。
- 在责任链模式中的体现:具体处理者类继承自抽象处理者类,它们可以完全替换抽象处理者类在责任链中的位置。客户端代码只与抽象处理者类进行交互,不需要关心具体是哪个具体处理者类在处理请求。例如,在一个图形绘制的责任链中,不同类型的图形绘制处理者(如圆形绘制处理者、矩形绘制处理者等)都继承自抽象的图形绘制处理者,它们可以互相替换,不影响责任链的正常运行。
依赖倒置原则(Dependency Inversion Principle)
- 含义:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
- 在责任链模式中的体现:客户端代码(高层模块)不直接依赖具体的处理者类(低层模块),而是依赖抽象处理者类。具体处理者类也依赖于抽象处理者类来定义其行为。这样,当具体处理者类发生变化时,不会影响到客户端代码。例如,在一个订单处理的责任链中,客户端只需要与抽象的订单处理者接口进行交互,而具体的订单处理者(如折扣处理者、库存检查处理者等)都实现该接口,降低了模块之间的耦合度。
迪米特法则(Law of Demeter)
- 含义:一个对象应该对其他对象有最少的了解,即一个类应该尽量减少与其他类的交互。
- 在责任链模式中的体现:每个具体处理者只与它的下一个处理者进行交互,不需要了解责任链中其他处理者的具体实现。处理者只负责处理自己能够处理的请求,如果不能处理则将请求传递给下一个处理者,避免了处理者之间的过度依赖和复杂的交互关系。例如,在一个电商系统的优惠券处理责任链中,每个优惠券处理者只知道如何处理自己负责的优惠券类型以及将不处理的请求传递给下一个处理者,不需要了解其他优惠券处理者的细节。
责任链模式的优缺点
优点
1. 解耦请求发送者和接收者
- 请求的发送者只需要将请求发送到责任链上,而不需要知道具体是哪个处理者会处理这个请求。处理者之间的关系由责任链来维护,它们只需要关注自身的处理逻辑和如何将请求传递给下一个处理者。
- 例如,在一个公司的请假审批系统中,员工(请求发送者)只需要提交请假申请,不需要知道是组长、部门经理还是总经理最终会批准这个申请,降低了员工与各级审批者之间的耦合度。
2. 增强灵活性和可扩展性
- 可以很方便地增加或删除处理者,或者改变处理者的顺序,而不需要对客户端代码进行大量修改。新的处理者只需要实现抽象处理者接口,并按照规则添加到责任链中即可。
- 比如,在一个电商系统的订单处理流程中,如果需要增加一个新的处理环节,如对特定商品订单进行额外的审核,只需要创建一个新的处理者类并插入到合适的位置,不会影响原有的订单处理逻辑。
3. 简化对象之间的交互
- 避免了多个对象之间相互引用、相互依赖的复杂关系。每个处理者只需要关心自己是否能够处理请求以及如何将请求传递给下一个处理者,使得代码结构更加清晰,易于理解和维护。
- 以一个游戏中的事件处理系统为例,鼠标点击事件可能会经过多个组件的处理,使用责任链模式可以让每个组件只专注于自己的处理逻辑,而不需要了解其他组件的具体情况。
4. 符合开闭原则
- 对扩展开放,对修改关闭。当需要添加新的处理者时,只需要创建新的具体处理者类并加入到责任链中,而不需要修改现有的处理者类和客户端代码。
- 例如,在一个日志处理系统中,如果需要增加一种新的日志过滤规则,只需要创建一个新的日志过滤器类并添加到责任链中,不会影响原有的日志处理流程。
缺点
1. 请求可能得不到处理
- 如果责任链没有正确配置,或者链中没有合适的处理者能够处理请求,那么请求可能会一直传递到链的末端,最终得不到处理。这可能会导致一些潜在的问题,需要在代码中进行额外的处理。
- 比如,在一个文件处理的责任链中,如果所有的处理者都无法处理某种类型的文件,而又没有相应的错误处理机制,那么这个文件的处理请求就会被忽略。
2. 调试难度增加
- 由于请求在责任链中传递,当出现问题时,很难确定是哪个处理者出现了问题。需要逐步跟踪请求的传递过程,排查每个处理者的处理逻辑,这增加了调试的难度和时间成本。
- 例如,在一个复杂的业务流程中,一个请求经过多个处理者的处理后出现了错误,需要逐个检查每个处理者的输入和输出,才能找到问题所在。
3. 性能问题
- 如果责任链过长,请求的传递和处理会涉及多个对象的调用,这会增加系统的开销,降低性能。特别是在处理大量请求时,性能问题可能会更加明显。
- 比如,在一个实时性要求较高的系统中,一个请求在经过多个处理者的传递和处理后,可能会导致响应时间过长,影响用户体验。
4. 代码复杂度提升
- 责任链模式需要定义抽象处理者、具体处理者等多个类,并且需要正确配置责任链,这会增加代码的复杂度。对于一些简单的业务场景,使用责任链模式可能会显得过于繁琐。
- 例如,在一个只需要简单处理几个固定类型请求的系统中,使用责任链模式会引入不必要的类和代码,增加开发和维护的难度。
注意事项
责任链的配置与管理
- 正确构建责任链
- 要确保责任链中各个处理者的顺序正确,符合业务逻辑。例如在请假审批流程中,应该按照从基层领导到高层领导的顺序依次构建责任链,若顺序错误,可能导致低权限的领导处理了本应由高权限领导处理的请求,或者请求无法得到正确处理。
- 责任链的起点和终点要明确,避免出现循环引用或者无终止的情况。如果处理者之间形成了循环引用,请求会在链中不断循环,导致系统资源耗尽。
- 动态调整责任链
- 当业务需求发生变化时,可能需要动态地添加、删除或修改责任链中的处理者。这就要求在设计时考虑如何方便地进行这些操作,同时要保证责任链的完整性和正确性。例如,在一个电商订单处理系统中,促销活动期间可能需要临时增加一个优惠券处理环节到责任链中。
请求处理与传递
- 请求的完整性
- 在责任链中传递的请求对象应该包含足够的信息,以便每个处理者能够根据这些信息做出正确的处理决策。如果请求信息不完整,可能会导致处理者无法正确处理请求或者做出错误的判断。例如,在一个文件处理责任链中,请求对象应包含文件的类型、大小等关键信息。
- 避免请求丢失
- 要确保每个处理者在无法处理请求时,能够正确地将请求传递给下一个处理者。如果某个处理者在不能处理请求时没有正确传递,请求可能会丢失,导致业务流程中断。可以在抽象处理者中提供默认的传递方法,确保请求能正常流转。
- 处理结果的反馈
- 考虑如何将处理结果反馈给客户端。有些情况下,客户端可能需要知道请求是否被成功处理以及处理的具体结果。可以在处理者处理完请求后,通过返回值或者回调函数的方式将结果反馈给客户端。
性能与调试
- 性能开销
- 责任链过长会增加请求处理的时间和系统开销,因为请求需要依次经过多个处理者。在设计责任链时,要权衡责任链的长度,避免不必要的处理者加入链中。可以通过性能测试来评估责任链的性能,并根据结果进行优化。
- 调试难度
- 由于请求在责任链中传递,调试时很难确定是哪个处理者出现了问题。可以在处理者中添加日志记录,记录请求的接收、处理和传递情况,方便在出现问题时进行排查。同时,也可以使用调试工具来跟踪请求的传递过程。
异常处理
- 处理者内部异常
- 每个处理者在处理请求时可能会抛出异常,需要在处理者内部进行适当的异常处理。例如,在一个数据库操作的责任链中,某个处理者在执行数据库查询时可能会出现数据库连接异常,处理者应该捕获并处理这些异常,避免异常影响到整个责任链的执行。
- 全局异常处理
- 除了处理者内部的异常处理,还需要考虑全局的异常处理机制。当整个责任链都无法处理请求或者出现严重错误时,要有相应的处理方式,如返回错误信息给客户端或者进行系统级别的日志记录。
应用场景
审批流程类场景
- 公司请假与报销审批:在公司中,员工请假或申请报销时,通常需要经过多个层级的审批。例如,请假天数较少时可能只需直属领导批准,请假天数较多则需要部门经理、总经理等依次审批。每个审批者就是一个处理者,构成了一条责任链。请求(请假或报销申请)从员工发起,沿着责任链依次传递,直到有合适的审批者处理该请求。
- 项目立项审批:大型项目的立项需要经过多个部门和层级的评估与审批,如业务部门、财务部门、技术部门等。每个部门都有自己的审批职责和权限,项目立项申请在各部门之间依次流转,形成一个责任链。
事件处理类场景
- 图形界面事件处理:在图形界面应用程序中,鼠标点击、键盘输入等事件可能需要经过多个组件或层级的处理。例如,当用户点击一个按钮时,事件首先由按钮组件处理,如果按钮组件不处理,可能会传递给父容器组件,再到窗口组件等,形成一条事件处理的责任链。
- 游戏中的事件响应:在游戏开发中,各种游戏事件(如玩家攻击、角色受伤等)可能需要经过不同的系统进行处理。例如,玩家攻击事件可能先由战斗系统判断攻击是否有效,再由伤害计算系统计算伤害值,最后由角色状态系统更新角色的生命值等,这些系统构成了一个事件处理的责任链。
过滤器与拦截器场景
- Web 应用请求过滤:在 Web 应用中,一个 HTTP 请求可能需要经过多个过滤器的处理,如身份验证过滤器、权限检查过滤器、数据加密过滤器等。每个过滤器都有自己的职责,按照顺序构成责任链对请求进行处理。只有通过前面过滤器的请求才能继续传递到下一个过滤器,直到最终到达业务处理逻辑。
- 日志处理过滤器:在日志系统中,日志信息可能需要经过多个过滤器的处理,如日志级别过滤、日志内容过滤等。不同的过滤器可以根据不同的规则对日志信息进行筛选和处理,确保只有符合条件的日志信息才会被记录或输出。
错误处理与重试机制场景
- 分布式系统中的错误处理:在分布式系统中,当某个服务调用失败时,可能需要进行一系列的错误处理和重试操作。例如,首先尝试进行本地重试,若本地重试失败,则尝试切换到备用服务,若备用服务也不可用,则通知管理员等。这些错误处理步骤可以构成一个责任链,依次对错误进行处理。
- 网络请求重试机制:在进行网络请求时,可能会因为网络波动等原因导致请求失败。可以构建一个责任链,其中每个处理者负责不同类型的重试策略,如指数退避重试、固定次数重试等。当请求失败时,请求会依次经过这些处理者,尝试不同的重试方式,直到请求成功或达到最大重试次数。
数据处理与转换场景
- 数据清洗与转换流程:在数据处理过程中,原始数据可能需要经过多个步骤的清洗和转换。例如,先去除数据中的空值,再进行数据格式转换,最后进行数据加密等。每个步骤可以看作一个处理者,构成责任链对数据进行逐步处理。
- 文档处理流程:在文档处理中,文档可能需要经过多个处理环节,如格式检查、内容审核、排版调整等。每个处理环节都有自己的处理逻辑,形成一个责任链对文档进行处理。
相关文章:
设计模式之责任链模式
引言 在职场中,请假流程大家都再熟悉不过:申请 1 至 2 天的假期,只需直属主管审批即可;若要请假 3 至 5 天,就需部门负责人进行复核;而超过 5 天的假期申请,则必须由总经理最终定夺。要是遇到超…...
AndroidStudio下载旧版本方法
首先,打开Android Studio的官网:https://developer.android.com/studio。 然后,点击【Read release notes】。 然后需要将语言切换成英文,否则会刷不出来。 然后就可以看下各个历史版本了。 直接点链接好像也行:h…...
Excel基础(详细篇):总结易忽视的知识点,有用的细节操作
目录 基础篇Excel主要功能必会快捷键LotusExcel的文件类型工作表基本操作表项操作选中与缩放边框线 自动添加边框线格式刷设置斜线表头双/多斜线表头不变形的:双/多斜线表头插入多行、多列单元格/行列的移动冻结窗口 方便查看数据打印的常见问题Excel格式数字格式日期格式文本…...
FPGA开发,使用Deepseek V3还是R1(7):以“FPGA的整体设计框架”为例
以下都是Deepseek生成的答案 FPGA开发,使用Deepseek V3还是R1(1):应用场景 FPGA开发,使用Deepseek V3还是R1(2):V3和R1的区别 FPGA开发,使用Deepseek V3还是R1&#x…...
Android15 Camera HAL Android.bp中引用Android.mk编译的libB.so
背景描述 Android15 Camera HAL使用Android.bp脚本来构建系统。假设Camera HAL中引用了另外一个HAL实现的so (例如VPU HAL), 恰巧被引用的这个VPU HAL so是用Android.mk构建的,那Camera HAL Android.bp在直接引用这个Android.mk编…...
服务流程设计和服务或端口重定向及其websocket等应用示例
服务流程设计和服务或端口重定向及其websocket等应用示例 目录 服务或端口重定向的服务设计和websocket等应用示例 一、通用请求控制流程 1.1、入口 1.2、所有GET请求首先预检控制单元 1.3、http请求会分别自动307重定向 1.4、所有请求首先执行跨源控制单元 1.5、然后…...
(十 五)趣学设计模式 之 命令模式!
目录 一、 啥是命令模式?二、 为什么要用命令模式?三、 策略模式的实现方式四、 命令模式的优缺点五、 命令模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支…...
计算机毕设JAVA——某高校宿舍管理系统(基于SpringBoot+Vue前后端分离的项目)
文章目录 概要项目演示图片系统架构技术运行环境系统功能简介 概要 网络上许多计算机毕设项目开发前端界面设计复杂、不美观,而且功能结构十分单一,存在很多雷同的项目:不同的项目基本上就是套用固定模板,换个颜色、改个文字&…...
点云配准技术的演进与前沿探索:从传统算法到深度学习融合(3)
3、基于深度学习的点云配准方法 3.1 深度学习在点云配准中的应用原理 深度学习作为一种强大的机器学习技术,近年来在点云配准领域展现出了巨大的潜力和优势。其核心在于通过构建复杂的神经网络模型,能够自动从大量的点云数据中学习到高度抽象且有效的特…...
MyBatis TypeHandler 详解与实战:FastJson 实现字符串转 List
在 MyBatis 中,TypeHandler 是实现 Java 类型与数据库类型双向转换 的核心组件。无论是处理基础数据类型还是复杂的 JSON、枚举或自定义对象,它都能通过灵活的扩展机制满足开发需求。本文将通过一个 将数据库 JSON 字符串转换为 List<User> 的案例…...
实验环境搭建集锦(docker linux ros2+强化学习环境+linux上单片机串口调试)
为了记住一些实验环境配置开的文章,边配置边记,免得之后忘了。 Docker环境搭建 yay -S docker //下载docker docker info //查看docker配置 sudo systemctl start docker //系统配置打开docker sudo systemctl enable docker //系统配置后台开启d…...
第十三届蓝桥杯大赛软件赛决赛C/C++ 大学 B 组
A 【2022——暴力DP / 优雅背包】-CSDN博客 B 【钟表——类日期问题】-CSDN博客 C 【卡牌——二分】-CSDN博客 D 【最大数字——DFS】-CSDN博客 E 【出差——Dijkstra】-CSDN博客 F 【费用报销——01背包】-CSDN博客 G 【故障——条件概率】-CSDN博客 H 【机房—…...
商城系统单商户开源版源码
环境配置 1.软件安装 宝塔安装系统软件:Nginx、MySQL5.6、PHP( PHP用7.1-7.4版本)、phpMyAdmin(Web端MySQL管理工具)。 2.配置mysql 设置mysql,在已安装的软件里面找到 mysql点击进行设置 3.修改sql-mode 选择左侧配置修改,找到里面的sql-mode&…...
【SpringBoot+Vue】博客项目开发二:用户登录注册模块
后端用户模块开发 制定参数交互约束 当前,我们使用MybatisX工具快速生成的代码中,包含了一个实体类,这个类中包含我们数据表中的所有字段。 但因为有些字段,是不应该返回到前端的,比如用户密码,或者前端传…...
HTTP 协议的发展历程:从 HTTP/1.0 到 HTTP/2.0
HTTP 协议的发展历程:从 HTTP/1.0 到 HTTP/2.0 HTTP(HyperText Transfer Protocol,超文本传输协议)是 Web 的基础协议,用于客户端和服务器之间的通信。从 HTTP/1.0 到 HTTP/2.0,HTTP 协议经历了多次重大改…...
每日十个计算机专有名词 (7)
Metasploit 词源:Meta(超越,超出) exploit(漏洞利用) Metasploit 是一个安全测试框架,用来帮助安全专家(也叫渗透测试人员)发现和利用计算机系统中的漏洞。你可以把它想…...
SQL经典题型
查询不在表里的数据,一张学生表,一张学生的选课表,要求查出没有选课的学生? select students.student_name from students left join course_selection on students.student_idcourse_selection.student_id where course_selecti…...
基于Linux系统的物联网智能终端
背景 产品研发和项目研发有什么区别?一个令人发指的问题,刚开始工作时项目开发居多,认为项目开发和产品开发区别不大,待后来随着自身能力的提升,逐步感到要开发一个好产品还是比较难的,我认为项目开发的目的…...
文字描边实现内黄外绿效果
网页使用 <!DOCTYPE html> <html> <head> <style> .text-effect {color: #ffd700; /* 黄色文字 */-webkit-text-stroke: 2px #008000; /* 绿色描边(兼容Webkit内核) */text-stroke: 2px #008000; /* 标准语法 *…...
next实现原理
Next.js 是一个基于 React 的 服务器端渲染(SSR) 和 静态生成(SSG) 框架,它的实现原理涉及多个关键技术点,包括 服务端渲染(SSR)、静态生成(SSG)、客户端渲染…...
什么是 jQuery
一、jQuery 基础入门 (一)什么是 jQuery jQuery 本质上是一个快速、小巧且功能丰富的 JavaScript 库。它将 JavaScript 中常用的功能代码进行了封装,为开发者提供了一套简洁、高效的 API,涵盖了 HTML 文档遍历与操作、事件处理、…...
014 rocketmq角色介绍
文章目录 NameServer1 服务发现机制2 为什么要使⽤NameServer3 NameServer如何保证数据的最终⼀致?4 特点 BrokerProducerConsumerTopicQueueProducer GroupConsumer GroupMessageTagOffset 同一消费者组下,队列只能由一个消费者消费 广播模式࿱…...
如何防止Python网络爬虫爬取网站内容
要防止Python网络爬虫爬取网站内容,可以从以下几个方面入手: 遵守Robots.txt文件:首先,网站管理员可以通过robots.txt文件明确告知爬虫哪些页面可以抓取,哪些不可以。爬虫在抓取之前应先检查该文件,尊重网站…...
项目准备(flask+pyhon+MachineLearning)- 3
目录 1.商品信息 2. 商品销售预测 2.1 机器学习 2.2 预测功能 3. 模型评估 1.商品信息 app.route(/products) def products():"""商品分析页面"""data load_data()# 计算当前期间和上期间current_period data[data[成交时间] > data[成…...
选开源CMS建站系统时,插件越多越好吗?
在选择开源CMS建站系统时,插件数量并不是唯一的衡量标准,更不能简单地说“插件越多就越好”,还是需要综合评估来考虑选择结果,以下是有关选择开源CMS系统时对插件数量的考量。 插件数量的优势插件数量可能带来的问题功能丰富性&a…...
OSPF BIT 类型说明
注:本文为 “OSPF BIT 类型 | LSA 类型 ” 相关文章合辑。 机翻,未校。 15 OSPF BIT Types Explained 15 种 OSPF BIT 类型说明 Rashmi Bhardwaj Distribution of routing information within a single autonomous system in larger networks is per…...
C语言(3)—循环、数组、函数的详解
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、函数二、循环与数组 1.循环2.数组 总结 前言 提示:以下是本篇文章正文内容,下面案例可供参考 一、函数 在C语言中,函数…...
大唐杯——阶段二01
03 5G寻呼 UE(User Equipment) UE是用户设备(User Equipment)的缩写,指的是移动通信网络中的终端设备,例如手机、平板电脑、物联网传感器等。 AMF(Access and Mobility Management Function&a…...
清华大学Deepseek第六版AIGC发展研究3.0(共186页,附PDF下载)
人工智能生成内容(AIGC)正以前所未有的速度改变我们的生活。 2024年底,清华大学新闻与传播学院与人工智能学院联合发布了《AIGC发展研究3.0版》,这份报告系统梳理了AIGC技术的突破性进展、应用场景及社会影响,并展望了…...
【漫话机器学习系列】114.逻辑 Sigmoid 函数
逻辑 Sigmoid 函数详解 1. 引言 逻辑回归(Logistic Regression)是机器学习中常用的分类算法,而 Sigmoid 函数 是逻辑回归的核心数学工具。Sigmoid 函数能够将任意实数映射到 (0,1) 之间,因此特别适用于概率估计。在这篇文章中&a…...
Cocos Creator3.8.6拖拽物体的几种方式
文章目录 前言一、第一种通过UILocation二、第二种通过UIDelta实现总结 前言 在游戏开发中,拖拽物体是一个非常常见的交互功能,无论是用于UI元素的拖动,还是场景中物体的移动,拖拽操作都能极大地提升用户体验。Cocos Creator 3.8…...
01_NLP基础之文本处理的基本方法
自然语言处理入门 自然语言处理(Natural Language Processing, 简称NLP)是计算机科学与语言学中关注于计算机与人类语言间转换的领域,主要目标是让机器能够理解和生成自然语言,这样人们可以通过语言与计算机进行更自然的互动。 …...
Minio搭建并在SpringBoot中使用完成用户头像的上传
Minio使用搭建并上传用户头像到服务器操作,学习笔记 Minio介绍 minio官网 MinIO是一个开源的分布式对象存储服务器,支持S3协议并且可以在多节点上实现数据的高可用和容错。它采用Go语言开发,拥有轻量级、高性能、易部署等特点,并且可以自由…...
深入解析 Kubernetes CRD:原理、特点与典型应用场景
深入解析 Kubernetes CRD:原理、特点与典型应用场景 一、CRD 的本质与原理 1.1 什么是 CRD? CRD(Custom Resource Definition) 是 Kubernetes 提供的核心扩展机制,允许用户自定义 API 资源类型。通过 CRD,开发者可以将业务逻辑抽象为 Kubernetes 原生资源模型,实现与…...
【新手入门】SQL注入之盲注
一、引言 在我们的注入语句被带入数据库查询但却什么都没有返回的情况我们该怎么办? 例如应用程序返回到一个"通用的"的页面,或者重定向一个通用页面(可能为网站首页)。这时,我们之前学习的SQL注入的办法就无法使用了。这种情况我们称之为无…...
功能丰富的自动化任务软件zTasker_2.1.0_绿色版_屏蔽强制更新闪退
🚀 zTasker 一键式效率倍增器使用指南 🙏 致谢 首先感谢开发者提供如此高效的工具! 软件本身功能强大,但部分机制需特别注意! 📖 软件概述 zTasker 是一款通过自动化脚本/任务流实现效率飞跃的生产力工…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-model.py
model.py ultralytics\models\yolo\model.py 目录 model.py 1.所需的库和模块 2.class YOLO(Model): 3.class YOLOWorld(Model): 1.所需的库和模块 # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/licensefrom pathlib import Pathfrom ult…...
深入浅出 Go 语言:协程(Goroutine)详解
深入浅出 Go 语言:协程(Goroutine)详解 引言 Go 语言的协程(goroutine)是其并发模型的核心特性之一。协程允许你轻松地编写并发代码,而不需要复杂的线程管理和锁机制。通过协程,你可以同时执行多个任务,并…...
【深度学习】Hopfield网络:模拟联想记忆
Hopfield网络是一种经典的循环神经网络,由物理学家John Hopfield在1982年提出。它的核心功能是模拟联想记忆,类似于人类大脑通过部分信息回忆完整记忆的能力。以下是通俗易懂的解释: 1. 核心思想 想象你看到一张模糊的老照片,虽然…...
为什么深度学习选择Tensor而非NumPy数组?核心优势深度解析
简短总结: 支持 GPU 加速:Tensor 提供对 GPU 的原生支持,能够有效加速计算,而 NumPy 则通常只能在 CPU 上运行。支持自动求导:深度学习模型的训练依赖于参数的优化,而 Tensor 提供了自动求导功能ÿ…...
MongoDB—(一主、一从、一仲裁)副本集搭建
MongoDB集群介绍: MongoDB 副本集是由多个MongoDB实例组成的集群,其中包含一个主节点(Primary)和多个从节点(Secondary),用于提供数据冗余和高可用性。以下是搭建 MongoDB 副本集的详细步骤&am…...
【Leetcode 每日一题】132. 分割回文串 II
问题背景 给你一个字符串 s s s,请你将 s s s 分割成一些子串,使每个子串都是回文串。 返回符合要求的 最少分割次数 。 数据约束 1 ≤ s . l e n g t h ≤ 2000 1 \le s.length \le 2000 1≤s.length≤2000 s s s 仅由小写英文字母组成 解题过程 …...
面试常问的压力测试问题
性能测试作为软件开发中的关键环节,确保系统在高负载下仍能高效运行。压力测试作为性能测试的重要类型,旨在通过施加超出正常负载的压力,观察系统在极端条件下的表现。面试中,相关问题常被问及,包括定义、重要性、与负…...
信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效
随着数据传输、存储及信息技术的飞速发展,信息安全保护已成为重中之重。各安全领域对跨网数据交互的需求日益迫切,数据传输的安全可靠性成为不可忽视的关键。为满足业务需求并遵守保密规范,针对于涉及重要秘密信息,需做到安全的物…...
MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别?
在 MySQL 的 InnoDB 存储引擎中,聚簇索引(Clustered Index)和非聚簇索引(Non-Clustered Index)是两种重要的索引类型,它们在数据存储结构、性能特点和适用场景上存在显著区别。以下是对它们的详细对比和解释…...
微信小程序开发学习笔记
微信小程序开发学习笔记 一、基础结构项目结构配置文件(app.json) 二、常用组件视图组件表单组件导航组件 三、API 常用功能网络请求数据缓存用户信息支付功能 四、框架与工具框架开发者工具 五、开发流程六、最佳实践七、常见问题路由跳转:权…...
动态规划刷题
文章目录 动态规划三步问题题目解析代码 动态规划 1. 状态表示:dp[i],表示dp表中i下标位置的值 2. 状态转移方程:以i位置位置的状态,最近的一步来划分问题,比如可以将状态拆分成前状态来表示现状态,dp[i] …...
uniapp 系统学习,从入门到实战(七)—— 网络请求与数据交互
全篇大概 3600 字(含代码),建议阅读时间 25min 📚 目录 使用uni.request发起请求封装全局请求工具破解跨域难题总结 在跨平台应用开发中,网络请求是连接前端与后端服务的核心环节。UniApp 提供了 uni.request 方法处理网络请求,但…...
AI人工智能机器学习之聚类分析
1、概要 本篇学习AI人工智能机器学习之聚类分析,以KMeans、AgglomerativeClustering、DBSCAN为例,从代码层面讲述机器学习中的聚类分析。 2、聚类分析 - 简介 聚类分析是一种无监督学习的方法,用于将数据集中的样本划分为不同的组ÿ…...
安当全栈式PostgreSQL数据库安全解决方案:透明加密、动态凭据与勒索防护一体化实践
引言:数字化转型下的数据库安全挑战 随着PostgreSQL在企业核心业务中的广泛应用,其承载的敏感数据价值日益攀升。然而,近年来针对数据库的攻击事件频发,如SQL注入漏洞(CVE-2025-1094)、勒索病毒攻击、内部…...