生产环境中常用的设计模式
生产环境中常用的设计模式
| 设计模式 | 目的 | 使用场景示例 |
|---|---|---|
| 单例模式 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点 | - 日志记录器 - 配置管理器 |
| 工厂方法模式 | 定义一个创建对象的接口,让子类决定实例化哪个类 | - 各种工厂类(如视频游戏工厂模式创建不同类型的角色) |
| 抽象工厂模式 | 解决一个系列的工厂,用于创建一组相关或依赖的对象 | - GUI组件库 - 汽车组装线 |
| 建造者模式 | 分离对象的构建过程和表示,允许逐步构造一个复杂对象 | - 构建复杂对象,如SQL语句构建器 |
| 原型模式 | 通过复制现有的实例来创建新的实例 | - 需要频繁创建开销较大的对象时 |
| 适配器模式 | 将不兼容的接口转换为一个可以使用的兼容接口 | - 兼容旧的类库 - 第三方库的集成 |
| 桥接模式 | 分离抽象部分和实现部分,使它们可以独立地变化 | - 当一个类存在多个变化维度时 |
| 组合模式 | 将对象组合成树形结构以表示“部分-整体”的层次结构 | - 处理类似文件系统的层次结构 |
| 装饰器模式 | 动态地添加额外功能到一个对象,而不是通过继承 | - 扩展对象功能,而不改变其类 |
| 外观模式 | 为子系统中的一组接口提供一个统一的接口 | - 简化复杂的系统接口 |
| 享元模式 | 运用共享技术有效支持大量细粒度对象 | - 当系统中存在大量相似对象时 |
| 代理模式 | 为其他对象提供一个代理或占位符,以控制对该对象的访问 | - 访问控制 - 延迟初始化 |
| 责任链模式 | 使多个对象都有机会处理请求,避免耦合请求的发送者和接收者 | - 审批流程 - 错误处理 |
| 命令模式 | 将一个请求封装为一个对象,允许使用不同的请求参数化其他对象 | - 宏系统 - 事务系统 |
| 解释器模式 | 定义一个语言的文法,并建立解释器解释该语言中的句子 | - 解析表达式或指令 |
| 迭代器模式 | 提供一种顺序访问聚合对象元素的方法,而不暴露其内部表示 | - 遍历聚合对象,而不关心内部结构 |
| 中介者模式 | 用一个中介对象封装一系列对象之间的交互 | - 集中管理对象间的通信 |
| 备忘录模式 | 捕获对象的内部状态,并在外部保存该状态 | - 提供对象状态快照,用于撤销操作 |
| 观察者模式 | 对象间存在一对多关系时使用 | - 事件多级触发 - 发布/订阅系统 |
| 状态模式 | 对象在其内部状态改变时可改变行为 | - 行为随状态改变而变化的对象 |
| 策略模式 | 定义一系列算法,并将它们封装起来使其可以互换 | - 运行时动态选择算法 |
| 访问者模式 | 在不改变类前提下,定义作用于对象结构的新操作 | - 数据结构稳定,但需定义新操作 |
Spring 容器的作用与使用
Spring 容器是 Spring 框架的核心组件之一,用于管理 Java 对象的生命周期和依赖关系。它以 IoC(控制反转) 和 DI(依赖注入) 为核心思想,帮助开发者简化对象的创建、配置、管理及其依赖关系。
1. Spring 容器的核心概念
- 控制反转(IoC):
传统编程中,对象由程序主动创建并管理;而在 IoC 中,对象的创建和生命周期交由 Spring 容器管理,程序通过依赖注入获得所需的对象。 - 依赖注入(DI):
容器负责将需要的依赖(如服务类、工具类)注入到使用它们的组件中,而不需要显式创建这些依赖。
2. Spring 容器的主要实现
Spring 提供了两种核心的 IoC 容器实现:
BeanFactory:
基础容器,提供最基本的 IoC 功能,按需加载 Bean(延迟初始化)。适合资源受限的场景。ApplicationContext:
功能更丰富的容器,支持事件发布、国际化、Bean 生命周期回调等功能。常用实现包括:ClassPathXmlApplicationContext:通过 XML 配置文件加载 Bean。AnnotationConfigApplicationContext:通过注解配置加载 Bean。WebApplicationContext:专为 Web 应用设计的容器。
3. 使用 Spring 容器动态获取 Bean
通过 Spring 容器获取 Bean 的方式有多种,以下重点讲解动态获取的场景:
1. 使用 ApplicationContext.getBean 动态获取 Bean
ApplicationContext.getBean 方法可以根据名称或类型动态获取 Bean。例如:
@Autowired
private ApplicationContext applicationContext;public void getBeanExample() {// 根据 Bean 名称获取MyService myServiceByName = (MyService) applicationContext.getBean("myService");// 根据类型获取MyService myServiceByType = applicationContext.getBean(MyService.class);// 根据名称和类型获取MyService myServiceByNameAndType = applicationContext.getBean("myService", MyService.class);
}
2. 使用 @Autowired 注入
@Autowired 是 Spring 提供的注解,用于自动装配依赖。其本质也是通过容器获取对象并注入:
@Autowired
private MyService myService;public void useService() {myService.performTask();
}
4. 配置 Spring 容器的方式
1. 基于注解的配置
推荐使用注解方式配置 Spring 容器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfig {@Beanpublic MyService myService() {return new MyServiceImpl();}
}
加载 Spring 容器:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class SpringContainerExample {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);MyService myService = context.getBean(MyService.class);myService.performTask();}
}
2. 基于 XML 的配置
虽然 XML 配置方式较少使用,但仍有必要了解:
XML 配置文件(applicationContext.xml):
<beans xmlns="http://www.springframework.org/schema/beans" ...><bean id="myService" class="com.example.MyServiceImpl" />
</beans>
加载容器:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringContainerExample {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");MyService myService = (MyService) context.getBean("myService");myService.performTask();}
}
5. 使用场景中的优化策略
- 动态场景:
- 动态获取不同的实现类(如策略模式中的支付方式)。
- 配合
getBean方法动态注入,提高灵活性。
- 注解优先:
- 使用
@Component标注类,@Autowired注入依赖,减少配置代码。
- 使用
- 多环境支持:
- 结合
@Profile实现多环境配置。
- 结合
- 提升性能:
- 结合
@Lazy实现延迟加载,优化资源占用。
- 结合
6. 总结
Spring 容器通过 IoC 和 DI 大幅简化了应用程序中对象的管理和依赖的配置,极大提高了开发效率和代码质量。动态获取 Bean、注解配置等功能为复杂业务场景(如策略模式)提供了便捷的实现方式。
1.策略模式
1.1 业务场景
假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型的文件,采取不同的解析方式。多数的小伙伴就会写出以下的代码:
if(type=="A"){//按照A格式解析
}else if(type=="B"){//按B格式解析
}else{//按照默认格式解析
}
这个代码可能会存在哪些问题呢?
- 如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低。
- 如果你需要接入一种新的解析类型,那只能在原有代码上修改。
说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及单一原则。
- 开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码
- 单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。
如果你的代码就是酱紫:有多个if...else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
1.2 策略模式定义
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。这个策略模式的定义是不是有点抽象呢?那我们来看点通俗易懂的比喻:
假设你跟不同性格类型的小姐姐约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去逛街买买买最合适。当然,目的都是为了得到小姐姐的芳心,请看电影、吃小吃、逛街就是不同的策略。
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
1.3 策略模式使用
策略模式怎么使用呢?酱紫实现的:
- 定义策略接口:一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
- 实现不同的策略:不同策略的差异化实现(就是说,不同策略的实现类)
- 使用策略模式
1.3.1 定义策略接口:一个接口,两个方法
public interface IFileStrategy {//属于哪种文件解析类型FileTypeResolveEnum gainFileType();//封装的公用算法(具体的解析方法)void resolve(Object objectparam);
}
1.3.2 实现不同的策略
A 类型策略具体实现
@Component
public class AFileResolve implements IFileStrategy {private static final Logger logger = LoggerFactory.getLogger(AFileResolve.class);//1.匹配类型@Overridepublic FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_A_RESOLVE;}//2.封装的公用算法(具体的解析方法)@Overridepublic void resolve(Object objectparam) {logger.info("A 类型解析文件,参数:{}",objectparam);//A类型解析具体逻辑}
}
B 类型策略具体实现
@Component
public class BFileResolve implements IFileStrategy {private static final Logger logger = LoggerFactory.getLogger(BFileResolve.class);@Overridepublic FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_B_RESOLVE;}@Overridepublic void resolve(Object objectparam) {logger.info("B 类型解析文件,参数:{}",objectparam);//B类型解析具体逻辑}
}
默认类型策略具体实现
@Component
public class DefaultFileResolve implements IFileStrategy {private static final Logger logger = LoggerFactory.getLogger(DefaultFileResolve.class);@Overridepublic FileTypeResolveEnum gainFileType() {return FileTypeResolveEnum.File_DEFAULT_RESOLVE;}@Overridepublic void resolve(Object objectparam) {logger.info("默认类型解析文件,参数:{}",objectparam);//默认类型解析具体逻辑}
}
1.3.3 使用策略模式
如何使用呢?我们借助spring的生命周期,使用ApplicationContextAware接口,把对应的策略,初始化到map里面。然后对外提供resolveFile方法即可。
@Component
public class StrategyUseService implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) {IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum);if (iFileStrategy != null) {iFileStrategy.resolve(objectParam);}}//把不同策略放到map@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));}
}
2. 工厂模式
核心:
- 利用 Java多态 进行公共功能同一抽象
- 使用 父类接口 指向 子类对象 进行优化
- 根据不同类型 创建对应子类
3. 简单工厂模式
3.1 模式简介
简单工厂模式是一种创建型设计模式,用于通过一个工厂类,根据不同的输入参数,动态决定实例化哪一个类。它将对象的创建逻辑集中在一个工厂类中,从而提高代码的复用性和维护性。
3.2 模式结构
- 工厂类(Factory): 负责创建产品对象的逻辑。
- 抽象产品类(Product): 定义产品的公共接口。
- 具体产品类(ConcreteProduct): 实现抽象产品接口的具体对象。
3.3 实现步骤
3.3.1 定义产品接口和具体实现
定义一个
Shape接口,所有具体产品实现该接口。/*** 抽象产品接口*/ public interface Shape {void draw(); }/*** 具体产品:圆形*/ public class Circle implements Shape {@Overridepublic void draw() {System.out.println("绘制圆形");} }/*** 具体产品:矩形*/ public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("绘制矩形");} }/*** 具体产品:三角形*/ public class Triangle implements Shape {@Overridepublic void draw() {System.out.println("绘制三角形");} }
3.3.2 创建工厂类
工厂类包含逻辑,根据输入参数返回对应的具体产品实例。
/*** 简单工厂类*/ public class ShapeFactory {public static Shape createShape(String shapeType) {if ("circle".equalsIgnoreCase(shapeType)) {return new Circle();} else if ("rectangle".equalsIgnoreCase(shapeType)) {return new Rectangle();} else if ("triangle".equalsIgnoreCase(shapeType)) {return new Triangle();} else {throw new IllegalArgumentException("未知的图形类型:" + shapeType);}} }
3.3.3 测试简单工厂模式
编写测试类,验证简单工厂模式的功能。
public class SimpleFactoryTest {public static void main(String[] args) {// 创建各种图形Shape circle = ShapeFactory.createShape("circle");circle.draw();Shape rectangle = ShapeFactory.createShape("rectangle");rectangle.draw();Shape triangle = ShapeFactory.createShape("triangle");triangle.draw();} }
3.3.4 运行结果
运行测试代码后,会输出以下内容:
绘制圆形 绘制矩形 绘制三角形
4.4 优缺点分析
优点:
- 封装性强: 工厂类集中管理对象的创建逻辑,避免了重复代码。
- 可维护性好: 如果需要新增产品类型,只需修改工厂类,不影响客户端代码。
缺点:
- 违反开闭原则: 每次增加新产品,都需要修改工厂类。
- 单一职责压力大: 工厂类的职责过于集中,可能会成为系统的性能瓶颈。
3.5 优化方案
使用 工厂方法模式 可以解决简单工厂模式中违反开闭原则的问题。工厂方法模式通过定义多个具体工厂类,每个工厂类负责创建一种产品,符合开闭原则。
2.1 业务场景
工厂模式一般配合策略模式一起使用。用来去优化大量的if...else...或switch...case...条件语句。
我们就取第一小节中策略模式那个例子吧。根据不同的文件解析类型,创建不同的解析对象
IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){IFileStrategy fileStrategy ;if(fileType=FileTypeResolveEnum.File_A_RESOLVE){fileStrategy = new AFileResolve();}else if(fileType=FileTypeResolveEnum.File_A_RESOLV){fileStrategy = new BFileResolve();}else{fileStrategy = new DefaultFileResolve();}return fileStrategy;}
其实这就是工厂模式,定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
策略模式的例子,没有使用上一段代码,而是借助spring的特性,搞了一个工厂模式,哈哈,小伙伴们可以回去那个例子细品一下,我把代码再搬下来,小伙伴们再品一下吧:
@Component
public class StrategyUseService implements ApplicationContextAware{private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();//把所有的文件类型解析的对象,放到map,需要使用时,信手拈来即可。这就是工厂模式的一种体现啦@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));}
}
2.2 使用工厂模式
定义工厂模式也是比较简单的:
- 一个工厂接口,提供一个创建不同对象的方法。
- 其子类实现工厂接口,构造不同对象
- 使用工厂模式
2.3.1 一个工厂接口
interface IFileResolveFactory{void resolve();
}
2.3.2 不同子类实现工厂接口
class AFileResolve implements IFileResolveFactory{void resolve(){System.out.println("文件A类型解析");}
}class BFileResolve implements IFileResolveFactory{void resolve(){System.out.println("文件B类型解析");}
}class DefaultFileResolve implements IFileResolveFactory{void resolve(){System.out.println("默认文件类型解析");}
}
2.3.3 使用工厂模式
//构造不同的工厂对象
IFileResolveFactory fileResolveFactory;
if(fileType=“A”){fileResolveFactory = new AFileResolve();
}else if(fileType=“B”){fileResolveFactory = new BFileResolve();}else{fileResolveFactory = new DefaultFileResolve();
}fileResolveFactory.resolve();
一般情况下,对于工厂模式,你不会看到以上的代码。工厂模式会跟配合其他设计模式如策略模式一起出现的。
2.4 工厂方法模式 VS 简单工厂模式
| 特性 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 创建逻辑集中性 | 集中在一个工厂类中 | 分散到多个具体工厂类 |
| 符合开闭原则 | 不符合 | 符合 |
| 灵活性 | 较低 | 较高 |
| 类的数量 | 较少 | 较多 |
| 使用场景 | 产品种类较少,且变化较少的场景 | 产品种类较多,且变化频繁的场景 |
3. 责任链模式
3.1 业务场景
我们来看一个常见的业务场景:下订单。
下订单接口通常包含以下逻辑:
- 参数非空校验
- 安全校验
- 黑名单校验
- 规则拦截
很多开发者会直接使用异常来实现,但这种做法可能存在以下问题:
- 不便扩展:异常只能返回异常信息,扩展性差。
- 效率低下:用异常做逻辑判断效率低。
解决方式:使用责任链模式。
以下是传统异常实现的代码示例:
public class Order {public void checkNullParam(Object param) {// 参数非空校验throw new RuntimeException();}public void checkSecurity() {// 安全校验throw new RuntimeException();}public void checkBlackList() {// 黑名单校验throw new RuntimeException();}public void checkRule() {// 规则拦截throw new RuntimeException();}public static void main(String[] args) {Order order = new Order();try {order.checkNullParam(null); // 参数非空校验order.checkSecurity(); // 安全校验order.checkBlackList(); // 黑名单校验order.checkRule(); // 规则拦截System.out.println("Order success");} catch (RuntimeException e) {System.out.println("Order fail");}}
}
上述代码的问题包括:
- 使用异常进行逻辑判断,效率低且难以扩展。
- 异常设计初衷是处理意外情况,过度使用违背了设计原则。
这段代码使用了异常来做逻辑条件判断,如果后续逻辑越来越复杂的话,会出现一些问题:如异常只能返回异常信息,不能返回更多的字段,这时候需要自定义异常类。
【强制】 异常不要用来做流程控制,条件控制。说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
阿里开发手册规定:禁止用异常做逻辑判断。
优化方案:责任链模式。
3.2 责任链模式定义
当希望让多个对象有机会处理某个请求时,可以使用责任链模式。
定义:责任链模式为请求创建一个接收者对象的链,每个对象节点根据条件决定是否处理请求或将其传递给下一个节点。这样实现了请求的发送者和接收者的解耦。
责任链模式的核心是将处理逻辑串成链条,让请求依次传递。
定义:责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:

打个比喻:
假设你晚上去上选修课,为了可以走点走,坐到了最后一排。来到教室,发现前面坐了好几个漂亮的小姐姐,于是你找张纸条,写上:“你好, 可以做我的女朋友吗?如果不愿意请向前传”。纸条就一个接一个的传上去了,后来传到第一排的那个妹子手上,她把纸条交给老师,听说老师40多岁未婚…
3.3 责任链模式使用
核心步骤:
- 定义一个接口或抽象类。
- 每个责任节点实现接口或继承抽象类,进行差异化处理。
- 初始化责任链,将节点串联起来。
3.3.1 一个接口或者抽象类
这个接口或者抽象类,需要:
- 有一个指向责任下一个对象的属性
- 一个设置下一个对象的set方法
- 给子类对象差异化实现的方法(如以下代码的doFilter方法)
抽象类包含:
- 指向下一个处理节点的引用。
- 设置下一个处理节点的方法。
- 子类实现的处理逻辑方法。
public abstract class AbstractHandler {//责任链中的下一个对象private AbstractHandler nextHandler;/*** 责任链的下一个对象*/public void setNextHandler(AbstractHandler nextHandler){this.nextHandler = nextHandler;}/*** 具体参数拦截逻辑,给子类去实现*/public void filter(Request request, Response response) {doFilter(request, response);if (getNextHandler() != null) {getNextHandler().filter(request, response);}}public AbstractHandler getNextHandler() {return nextHandler;}abstract void doFilter(Request filterRequest, Response response);}
3.3.2 每个对象差异化处理
责任链上,每个对象的差异化处理,如本小节的业务场景,就有参数校验对象、安全校验对象、黑名单校验对象、规则拦截对象
/*** 参数校验对象**/
@Component
@Order(1) //顺序排第1,最先校验
public class CheckParamFilterObject extends AbstractHandler {@Overridepublic void doFilter(Request request, Response response) {System.out.println("非空参数检查");}
}/*** 安全校验对象*/
@Component
@Order(2) //校验顺序排第2
public class CheckSecurityFilterObject extends AbstractHandler {@Overridepublic void doFilter(Request request, Response response) {//invoke Security checkSystem.out.println("安全调用校验");}
}
/*** 黑名单校验对象*/
@Component
@Order(3) //校验顺序排第3
public class CheckBlackFilterObject extends AbstractHandler {@Overridepublic void doFilter(Request request, Response response) {//invoke black list checkSystem.out.println("校验黑名单");}
}/*** 规则拦截对象*/
@Component
@Order(4) //校验顺序排第4
public class CheckRuleFilterObject extends AbstractHandler {@Overridepublic void doFilter(Request request, Response response) {System.out.println("规则校验");}
}
3.3.3 对象链连起来(初始化)&& 使用
使用Spring的依赖注入将责任节点串联起来。
@Component("ChainPatternDemo")
public class ChainPatternDemo {//自动注入各个责任链的对象@Autowiredprivate List<AbstractHandler> abstractHandleList;private AbstractHandler abstractHandler;//spring注入后自动执行,责任链的对象连接起来@PostConstructpublic void initializeChainFilter(){for(int i = 0;i<abstractHandleList.size();i++){if(i == 0){abstractHandler = abstractHandleList.get(0);}else{AbstractHandler currentHander = abstractHandleList.get(i - 1);AbstractHandler nextHander = abstractHandleList.get(i);currentHander.setNextHandler(nextHander);}}}//直接调用这个方法使用public Response exec(Request request, Response response) {abstractHandler.filter(request, response);return response;}public AbstractHandler getAbstractHandler() {return abstractHandler;}public void setAbstractHandler(AbstractHandler abstractHandler) {this.abstractHandler = abstractHandler;}
}
运行结果
执行代码后输出:
非空参数检查
安全调用校验
校验黑名单
规则校验
4. 模板方法模式
核心思想
- 通过抽象类定义业务流程骨架,子类根据具体需求实现差异化代码。
- 约束子类的业务执行流程,保证流程的一致性和规范性。
实现步骤
- 定义模板方法的抽象类,包含固定流程的定义。
- 实现具体步骤,由子类实现差异化逻辑。
- 使用模板方法,调用统一的处理逻辑。
4.1 业务场景
假设一个内部系统需要处理不同商户的请求,与外部第三方系统进行 HTTP 通信。每个请求需要经过以下固定流程:
每个请求需要经过以下固定流程:
- 查询商户信息。
- 对请求报文加签。
- 发送 HTTP 请求。
- 对返回的报文验签。
此外,不同商户可能采用不同的通信方式:
- 商户 A 通过代理通信。
- 商户 B 直接连接。

原始实现问题
以 A 商户和 B 商户为例,原始实现如下:
// 商户 A 处理逻辑
class CompanyAHandler implements RequestHandler {public Resp handle(Req req) {//查询商户信息queryMerchantInfo();//加签signature();//http请求(A商户假设走的是代理)httpRequestbyProxy()//验签verify();return new Resp();}
}// 商户 B 处理逻辑
class CompanyBHandler implements RequestHandler {public Resp handle(Req req) {//查询商户信息queryMerchantInfo();//加签signature();// http请求(B商户不走代理,直连)httpRequestbyDirect();// 验签verify(); return new Resp();}
}
缺点:
- 代码重复:通用流程在多个子类中重复实现。
- 难以扩展:新增商户需要完整实现所有逻辑。
为了解决这些问题,可以使用模板方法模式。
4.2 模板方法模式定义
模板方法模式:
- 定义一个操作中的算法的骨架流程,把一些步骤的具体实现留给子类。
- 子类可以在不改变算法结构的前提下,重定义某些步骤。
核心思想:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。
通俗比喻:
模板方法模式 举例:追女朋友需要按顺序完成,牵手 → 拥抱 → 接吻 → 拍拍手。具体用左手还是右手牵,这种细节交给实现者决定,但整体流程固定。
4.3 模板方法使用
- 一个抽象类,定义骨架流程(抽象方法放一起)
- 确定的共同方法步骤,放到抽象类(去除抽象方法标记)
- 不确定的步骤,给子类去差异化实现
我们继续那以上的举例的业务流程例子,来一起用 模板方法优化一下哈:
4.3.1 定义抽象类
抽象类定义通用业务流程骨架:
/*** 抽象类定义业务流程骨架*/
abstract class AbstractMerchantService {// 1.查询商户信息abstract void queryMerchantInfo();// 2.加签abstract void signature();// 3.发送 HTTP 请求abstract void httpRequest();// 4.验签abstract void verifySignature();// 5.Http是否走代理(提供给子类实现)abstract boolean isRequestByProxy();/*** 模板方法:定义处理请求的流程*/public Resp handleTemplate(Req req) {queryMerchantInfo();signature();httpRequest();verifySignature();return new Resp();}
}
4.3.2 子类实现差异化逻辑
商户 A 的实现:
/*** 商户 A 实现类*/
class CompanyAServiceImpl extends AbstractMerchantService {@Overridevoid queryMerchantInfo() {System.out.println("查询商户 A 信息");}@Overridevoid signature() {System.out.println("对商户 A 请求报文加签");}@Overridevoid httpRequest() {System.out.println("通过代理发送商户 A 的 HTTP 请求");}@Overridevoid verifySignature() {System.out.println("验证商户 A 返回报文签名");}// 公司 A 走 http代理@Overrideboolean isRequestByProxy(){return true;}
}
商户 B 的实现:
/*** 商户 B 实现类*/
class CompanyBServiceImpl extends AbstractMerchantService {@Overridevoid queryMerchantInfo() {System.out.println("查询商户 B 信息");}@Overridevoid signature() {System.out.println("对商户 B 请求报文加签");}@Overridevoid httpRequest() {System.out.println("通过直连发送商户 B 的 HTTP 请求");}@Overridevoid verifySignature() {System.out.println("验证商户 B 返回报文签名");}// 公司 B 不走 http代理@Overrideboolean isRequestByProxy(){return false;}
}
4.3.3. 使用模板方法
调用模板方法处理请求:
public class Main {public static void main(String[] args) {AbstractMerchantService serviceA = new CompanyAServiceImpl();AbstractMerchantService serviceB = new CompanyBServiceImpl();// 处理商户 A 请求Resp respA = serviceA.handleTemplate(new Req());System.out.println("商户 A 请求处理完成");// 处理商户 B 请求Resp respB = serviceB.handleTemplate(new Req());System.out.println("商户 B 请求处理完成");}
}
5. 观察者模式(监听模式)
5.1 业务场景
登陆注册应该是最常见的业务场景了。就拿注册来说,我们经常会遇到类似的场景:用户注册成功后,需要给用户发送一条消息、邮件,或者其他通知。通常代码如下:
void register(User user){insertRegisterUser(user);sendIMMessage();sendEmail();
}
这段代码存在的问题是:
- 违反开闭原则:如果产品新增需求,例如注册成功后再给用户发送短信通知,就需要修改
register方法的代码。
void register(User user){insertRegisterUser(user);sendIMMessage();sendMobileMessage();sendEmail();
}
- 同步阻塞问题:如果调用“发送短信”的接口失败,可能会影响用户注册流程。
为了解决这些问题,可以使用观察者模式进行优化。
5.2 观察者模式定义
观察者模式:定义对象之间的一对多依赖关系,使得一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。
观察者模式属于行为型模式。一个对象(被观察者)的状态发生改变时,会通知所有依赖对象(观察者)。
核心成员
- 被观察者(Observerable):目标对象,状态发生变化时,通知所有观察者。
- 观察者(Observer):依赖被观察者,并响应其状态变化。
使用场景
- 事件异步通知:例如用户注册成功后,发送消息、邮件等操作。
5.3 观察者模式使用
观察者模式实现的话,还是比较简单的。
- 一个被观察者的类Observerable ;
- 多个观察者Observer ;
- 观察者的差异化实现
- 经典观察者模式封装:EventBus实战
5.3.1 被观察者类 Observerable
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;// 被观察者
public class Observerable {// 使用线程安全的Listprivate final List<Observer> observers = new CopyOnWriteArrayList<>();// 定义状态常量public static final int STATE_NOTIFY = 1;private int state;public int getState() {return state;}// 设置状态并通知观察者public void setState(int state) {this.state = state;notifyAllObservers();}// 添加观察者public void addObserver(Observer observer) {observers.add(observer);}// 移除观察者public void removeObserver(Observer observer) {observers.remove(observer);}// 通知所有观察者private void notifyAllObservers() {if (state != STATE_NOTIFY) {System.out.println("状态非通知状态,不触发通知。");return;}observers.forEach(Observer::doEvent);}
}
5.3.2 观察者接口 Observer 及其实现类
// 观察者接口
public interface Observer {void doEvent();
}// IM 消息观察者
public class IMMessageObserver implements Observer {@Overridepublic void doEvent() {System.out.println("发送 IM 消息");}
}// 短信观察者
public class MobileNoObserver implements Observer {@Overridepublic void doEvent() {System.out.println("发送短信消息");}
}// 邮件观察者
public class EmailObserver implements Observer {@Overridepublic void doEvent() {System.out.println("发送邮件消息");}
}
5.3.3 测试代码
public class ObserverPatternDemo {public static void main(String[] args) {Observerable observerable = new Observerable();// 添加观察者observerable.addObserver(new IMMessageObserver());observerable.addObserver(new MobileNoObserver());observerable.addObserver(new EmailObserver());// 设置状态并触发通知observerable.setState(Observerable.STATE_NOTIFY);}
}
5.3.4 使用 Guava EventBus 实现的观察者模式优化
直接实现观察者模式会略显繁琐,Google 提供的 Guava EventBus 封装了一套基于注解的事件总线,使用起来更加简单方便。
EventBus 中心类
import com.google.common.eventbus.EventBus;public class EventBusCenter {private static final EventBus EVENT_BUS = new EventBus();private EventBusCenter() {// 禁止实例化}public static EventBus getInstance() {return EVENT_BUS;}//添加观察者public static void register(Object obj) {EVENT_BUS.register(obj);}//移除观察者public static void unregister(Object obj) {EVENT_BUS.unregister(obj);}//把消息推给观察者public static void post(Object obj) {EVENT_BUS.post(obj);}
}
观察者类 EventListener
import com.google.common.eventbus.Subscribe;public class EventListener {@Subscribe //加了订阅,这里标记这个方法是事件处理方法 public void handle(NotifyEvent notifyEvent) {System.out.println("发送 IM 消息:" + notifyEvent.getImNo());System.out.println("发送短信消息:" + notifyEvent.getMobileNo());System.out.println("发送邮件消息:" + notifyEvent.getEmailNo());}
}
通知事件类 NotifyEvent
//通知事件类
public class NotifyEvent {private final String mobileNo;private final String emailNo;private final String imNo;public NotifyEvent(String mobileNo, String emailNo, String imNo) {this.mobileNo = mobileNo;this.emailNo = emailNo;this.imNo = imNo;}public String getMobileNo() {return mobileNo;}public String getEmailNo() {return emailNo;}public String getImNo() {return imNo;}
}
测试代码
public class EventBusDemoTest {public static void main(String[] args) {EventListener eventListener = new EventListener();EventBusCenter.register(eventListener);EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));}
}
public class EventBusDemoTest {public static void main(String[] args) {// 注册观察者EventListener eventListener = new EventListener();EventBusCenter.register(eventListener);// 发布通知事件NotifyEvent event = new NotifyEvent("13372817283", "123@qq.com", "666");EventBusCenter.post(event);}
}
输出结果
发送IM消息: 666
发送短信消息: 13372817283
发送Email消息: 123@qq.com
6. 单例模式
6.1 业务场景
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现de的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。
来看一个单例模式的例子
public class LanHanSingleton {private static LanHanSingleton instance;private LanHanSingleton(){}public static LanHanSingleton getInstance(){if (instance == null) {instance = new LanHanSingleton();}return instance;}}
以上的例子,就是懒汉式的单例实现。实例在需要用到的时候,才去创建,就比较懒。如果有则返回,没有则新建,需要加下 synchronized关键字,要不然可能存在线性安全问题。
6.2 单例模式的经典写法
其实单例模式还有有好几种实现方式,如饿汉模式,双重校验锁,静态内部类,枚举等实现方式。
6.2.1 饿汉模式
public class EHanSingleton {private static EHanSingleton instance = new EHanSingleton();private EHanSingleton(){ }public static EHanSingleton getInstance() {return instance;}}
饿汉模式,它比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
6.2.2 双重校验锁
public class DoubleCheckSingleton {private volatile static DoubleCheckSingleton instance;private DoubleCheckSingleton() { }public static DoubleCheckSingleton getInstance(){if (instance == null) {synchronized (DoubleCheckSingleton.class) {if (instance == null) {instance = new DoubleCheckSingleton();}}}return instance;}
}
双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在synchronized关键字内外都加了一层 if条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
6.2.3 静态内部类
public class InnerClassSingleton {private static class InnerClassSingletonHolder{private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();}private InnerClassSingleton(){}public static final InnerClassSingleton getInstance(){return InnerClassSingletonHolder.INSTANCE;}
}
静态内部类的实现方式,效果有点类似双重校验锁。但这种方式只适用于静态域场景,双重校验锁方式可在实例域需要延迟初始化时使用。
6.2.4 枚举
public enum SingletonEnum {INSTANCE;public SingletonEnum getInstance(){return INSTANCE;}
}
枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。
7.代理模式
代理模式(Proxy Pattern) 是一种结构型设计模式,通过为其他对象提供一种代理以控制对这个对象的访问。代理对象作为实际对象的代表,负责处理客户端与实际对象之间的交互。
7.1 模式简介
代理模式为实际对象提供了一个替代方案或占位符,可以在不改变实际对象的情况下增强功能、控制访问或延迟加载。常见的代理模式包括:
- 静态代理
- 动态代理
- JDK 动态代理
- CGLIB 动态代理
7.2 模式结构
代理模式包含以下主要角色:
- 抽象主题(Subject): 定义实际对象和代理对象的公共接口。
- 实际对象(RealSubject): 实现抽象主题接口的具体类,包含实际业务逻辑。
- 代理对象(Proxy): 通过实现抽象主题接口来代理实际对象,可以在调用实际对象方法时执行额外操作。
7.3 实现步骤
示例场景:用户服务的代理
假设有一个 UserService 接口,提供用户登录功能。我们使用代理模式来扩展日志记录功能。
7.3.1 静态代理实现
Step 1: 定义抽象主题和实际对象
定义 UserService 接口和实现类:
/*** 抽象主题:用户服务接口*/
public interface UserService {void login(String username, String password);
}/*** 实际对象:用户服务实现类*/
public class UserServiceImpl implements UserService {@Overridepublic void login(String username, String password) {System.out.println(username + " 登录成功!");}
}
Step 2: 创建代理对象
代理对象实现与实际对象相同的接口,并在调用实际对象方法时添加额外功能:
/*** 静态代理类:用户服务代理*/
public class UserServiceProxy implements UserService {private final UserService userService;// 通过构造函数传入实际对象public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic void login(String username, String password) {// 添加额外功能:日志记录System.out.println("日志记录:用户 " + username + " 尝试登录。");// 调用实际对象的方法userService.login(username, password);System.out.println("日志记录:用户 " + username + " 登录操作完成。");}
}
Step 3: 测试静态代理
测试代理功能:
public class StaticProxyTest {public static void main(String[] args) {// 创建实际对象UserService userService = new UserServiceImpl();// 创建代理对象UserService proxy = new UserServiceProxy(userService);// 使用代理对象proxy.login("Alice", "123456");}
}
运行结果:
日志记录:用户 Alice 尝试登录。
Alice 登录成功!
日志记录:用户 Alice 登录操作完成。
7.3.2 动态代理实现
动态代理 是在运行时动态生成代理类,无需手动为每个类编写代理代码。
JDK 动态代理
Step 1: 使用 InvocationHandler 接口
动态代理使用 InvocationHandler 处理方法调用:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class LoggingInvocationHandler implements InvocationHandler {private final Object target;// 构造函数接受实际对象public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强:日志记录System.out.println("日志记录:方法 " + method.getName() + " 开始执行。");// 调用实际对象的方法Object result = method.invoke(target, args);// 后置增强:日志记录System.out.println("日志记录:方法 " + method.getName() + " 执行完成。");return result;}
}
Step 2: 生成动态代理类
通过 Proxy.newProxyInstance 方法生成代理对象:
import java.lang.reflect.Proxy;public class DynamicProxyTest {public static void main(String[] args) {// 创建实际对象UserService userService = new UserServiceImpl();// 创建动态代理对象UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),new LoggingInvocationHandler(userService));// 使用代理对象proxy.login("Bob", "654321");}
}
运行结果:
日志记录:方法 login 开始执行。
Bob 登录成功!
日志记录:方法 login 执行完成。
CGLIB 动态代理
CGLIB 动态代理 使用字节码生成技术,可以代理没有实现接口的类。
依赖:
添加 cglib 和 asm 依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
实现:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** CGLIB 动态代理*/
public class CglibProxy implements MethodInterceptor {private final Object target;public CglibProxy(Object target) {this.target = target;}public Object getProxyInstance() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("日志记录:方法 " + method.getName() + " 开始执行。");Object result = proxy.invokeSuper(obj, args);System.out.println("日志记录:方法 " + method.getName() + " 执行完成。");return result;}
}
测试:
public class CglibProxyTest {public static void main(String[] args) {// 创建实际对象UserServiceImpl userService = new UserServiceImpl();// 创建代理对象CglibProxy proxy = new CglibProxy(userService);UserServiceImpl proxyInstance = (UserServiceImpl) proxy.getProxyInstance();// 使用代理对象proxyInstance.login("Charlie", "112233");}
}
7.4 优缺点分析
优点:
- 增强功能: 通过代理对象添加额外功能(如日志、权限检查等)。
- 解耦合: 客户端与实际对象之间的交互通过代理对象实现,便于维护。
- 灵活性: 动态代理可在运行时动态生成代理对象,无需手动编写代理类。
缺点:
- 性能开销: 动态代理涉及反射调用,性能略低于静态代理。
- 复杂性增加: 对新手开发者来说,动态代理的学习曲线较陡。
相关文章:
生产环境中常用的设计模式
生产环境中常用的设计模式 设计模式目的使用场景示例单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点- 日志记录器- 配置管理器工厂方法模式定义一个创建对象的接口,让子类决定实例化哪个类- 各种工厂类(如视频游戏工厂模式创…...
基于SpringBoot+Vue的药品管理系统【源码+文档+部署讲解】
系统介绍 基于SpringBootVue实现的药品管理系统采用前后端分离的架构方式,系统实现了用户登录、数据中心、药库管理、药房管理、物资管理、挂号管理、系统管理、基础设置等功能模块。 技术选型 开发工具:idea2020.3Webstorm2020.3 运行环境ÿ…...
【CompletableFuture实战】
CompletableFuture实战 前言 前言 过去的一年,匆匆忙忙,换了一次工作,写博客的习惯就落下了,总之,有点懈怠。希望今年能重拾信心,步入正规! CompletableFuture的用法网上资料颇多,…...
Redis 缓存穿透、击穿、雪崩 的区别与解决方案
前言 Redis 是一个高性能的键值数据库,广泛应用于缓存、会话存储、实时数据分析等场景。然而,在高并发的环境下,Redis 缓存可能会遇到 缓存击穿、缓存穿透 和 缓存雪崩 这三大问题。这些问题不仅影响系统的稳定性和性能,还经常出…...
Python自动化测试中定位隐藏菜单元素的策略
大家都读完觉得有帮助记得关注和点赞!!! 在进行Python自动化测试时,尤其是使用Selenium等工具对Web应用进行测试时,可能会遇到某些元素被隐藏的问题。这使得元素定位和交互变得复杂。然而,通过一些技术手段…...
【张雪峰高考志愿填报】合集
【张雪峰高考志愿填报】合集 链接:https://pan.quark.cn/s/89a2d88fa807 高考结束,分数即将揭晓,志愿填报的关键时刻近在眼前!同学们,这可是人生的重要转折点,选对志愿,就像为未来铺就一条…...
53,【3】BUUCTF WEB october 2019 Twice SQLinjection
题目得到信息,2次注入,进入靶场 登录页面,很自然想到SQL 第一次注入应该是这个可以登录,注册,提交简介的页面 第二次注入应该是在info处注入,信息显示在简介处 我真的纯脑子有病,人家二次注入不…...
【Linux系统】分区挂载
我们能够根据一个 inode 号在指定分区寻找目标文件的 struct inode,也能根据目录文件的内容,通过映射关系,找指定的 inode,可是,现在有个问题: 问题:inode 是不能跨分区使用的!Linu…...
Oracle 可观测最佳实践
简介 Oracle 数据库是一种广泛使用的商业关系数据库管理系统(RDBMS),由甲骨文公司(Oracle Corporation)开发。它支持 SQL 语言,能够存储和管理大量数据,并提供高级数据管理功能,如数…...
Ubuntu本地部署网站
目录 1.介绍 2.安装apache 3.网页升级 1.介绍 网站其实就相当于一个文件夹,用域名访问一个网页,就相当于访问了一台电脑的某一个文件夹,在网页中看见的视频,视频和音乐其实就是文件夹里面的文件。为什么网页看起来不像电脑文件夹…...
图数据库 | 18、高可用分布式设计(中)
上文我们聊了在设计高性能、高可用图数据库的时候,从单实例、单节点出发,一般有3种架构演进选项:主备高可用,今天我们具体讲讲分布式共识,以及大规模水平分布式。 主备高可用、分布式共识、大规模水平分布式ÿ…...
Java 读取 Windows 设备的唯一性标识及定位
在 Windows 系统中,获取设备唯一性标识及定位信息对设备管理、安全监控等场景意义重大。本文介绍 Java 中几种实现方法,如 JNA 库、WMI4Java 库及通过 JNI 结合 Windows API。 1. 使用 JNA 库读取 DEVPKEY_Device_ContainerId 在 Windows 系统中&…...
Spring boot框架下的RabbitMQ消息中间件
1. RabbitMQ 基础概念 1.1 消息处理流程与组件配合 Producer(生产者) 发送消息。消息先发送到 Exchange(交换机),而不是直接到队列。Exchange(交换机) 接收到消息后,根据 Routing …...
1 行命令引发的 Go 应用崩溃
一、前言 不久前,阿里云 ARMS 团队、编译器团队、MSE 团队携手合作,共同发布并开源了 Go 语言的编译时自动插桩技术。该技术以其零侵入的特性,为 Go 应用提供了与 Java 监控能力相媲美的解决方案。开发者只需将 go build 替换为新编译命令 o…...
ScratchLLMStepByStep:训练自己的Tokenizer
1. 引言 分词器是每个大语言模型必不可少的组件,但每个大语言模型的分词器几乎都不相同。如果要训练自己的分词器,可以使用huggingface的tokenizers框架,tokenizers包含以下主要组件: Tokenizer: 分词器的核心组件,定…...
G1原理—10.如何优化G1中的FGC
大纲 1.G1的FGC可以优化的点 2.一个bug导致的FGC(Kafka发送重试 subList导致List越来越大) 3.为什么G1的FGC比ParNew CMS要更严重 4.FGC的一些参数及优化思路 1.G1的FGC可以优化的点 (1)FGC的基本原理 (2)遇到FGC应该怎么处理 (3)应该如何操作来规避FGC (4)应该如何操…...
Java基础——概念和常识(语言特点、JVM、JDK、JRE、AOT/JIT等介绍)
我是一个计算机专业研0的学生卡蒙Camel🐫🐫🐫(刚保研) 记录每天学习过程(主要学习Java、python、人工智能),总结知识点(内容来自:自我总结网上借鉴࿰…...
2025.1.16——三、supersqli 绕过|堆叠注入|handler查询法|预编译绕过法|修改原查询法
题目来源:攻防世界supersqli 目录 一、打开靶机,整理已知信息 二、sqlmap解题 step 1:爆数据库 step 2:爆表 二、手工注入解题 step 1:判断注入类型 step 2:判断字段数 step 3:查询数据…...
浅谈计算机网络03 | 现代网络组成
现代网络组成 一 、网络生态体系1.1网络生态系统的多元主体1.2 网络接入设施的多样类型 二、现代网络的典型体系结构解析三、高速网络技术3.1 以太网技术3.2 Wi-Fi技术的深度剖析3.2.1 应用场景的多元覆盖3.2.2 标准升级与性能提升 3.3 4G/5G蜂窝网的技术演进3.3.1 蜂窝技术的代…...
Red Hat8:搭建FTP服务器
目录 一、匿名FTP访问 1、新建挂载文件 2、挂载 3、关闭防火墙 4、搭建yum源 5、安装VSFTPD 6、 打开配置文件 7、设置配置文件如下几个参数 8、重启vsftpd服务 9、进入图形化界面配置网络 10、查看IP地址 11、安装ftp服务 12、遇到拒绝连接 13、测试 二、本地…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
