生产环境中常用的设计模式
设计模式 | 目的 | 使用场景示例 |
---|---|---|
单例模式 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点 | - 日志记录器 - 配置管理器 |
工厂方法模式 | 定义一个创建对象的接口,让子类决定实例化哪个类 | - 各种工厂类(如视频游戏工厂模式创建不同类型的角色) |
抽象工厂模式 | 解决一个系列的工厂,用于创建一组相关或依赖的对象 | - 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 {
@Bean
public 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.匹配类型
@Override
public FileTypeResolveEnum gainFileType() {
return FileTypeResolveEnum.File_A_RESOLVE;
}
//2.封装的公用算法(具体的解析方法)
@Override
public 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);
@Override
public FileTypeResolveEnum gainFileType() {
return FileTypeResolveEnum.File_B_RESOLVE;
}
@Override
public void resolve(Object objectparam) {
logger.info("B 类型解析文件,参数:{}",objectparam);
//B类型解析具体逻辑
}
}
默认类型策略具体实现
@Component
public class DefaultFileResolve implements IFileStrategy {
private static final Logger logger = LoggerFactory.getLogger(DefaultFileResolve.class);
@Override
public FileTypeResolveEnum gainFileType() {
return FileTypeResolveEnum.File_DEFAULT_RESOLVE;
}
@Override
public 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
@Override
public 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 { @Override public void draw() { System.out.println("绘制圆形"); } } /** * 具体产品:矩形 */ public class Rectangle implements Shape { @Override public void draw() { System.out.println("绘制矩形"); } } /** * 具体产品:三角形 */ public class Triangle implements Shape { @Override public 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,需要使用时,信手拈来即可。这就是工厂模式的一种体现啦
@Override
public 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 {
@Override
public void doFilter(Request request, Response response) {
System.out.println("非空参数检查");
}
}
/**
* 安全校验对象
*/
@Component
@Order(2) //校验顺序排第2
public class CheckSecurityFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke Security check
System.out.println("安全调用校验");
}
}
/**
* 黑名单校验对象
*/
@Component
@Order(3) //校验顺序排第3
public class CheckBlackFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
//invoke black list check
System.out.println("校验黑名单");
}
}
/**
* 规则拦截对象
*/
@Component
@Order(4) //校验顺序排第4
public class CheckRuleFilterObject extends AbstractHandler {
@Override
public void doFilter(Request request, Response response) {
System.out.println("规则校验");
}
}
3.3.3 对象链连起来(初始化)&& 使用
使用Spring的依赖注入将责任节点串联起来。
@Component("ChainPatternDemo")
public class ChainPatternDemo {
//自动注入各个责任链的对象
@Autowired
private List<AbstractHandler> abstractHandleList;
private AbstractHandler abstractHandler;
//spring注入后自动执行,责任链的对象连接起来
@PostConstruct
public 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 {
@Override
void queryMerchantInfo() {
System.out.println("查询商户 A 信息");
}
@Override
void signature() {
System.out.println("对商户 A 请求报文加签");
}
@Override
void httpRequest() {
System.out.println("通过代理发送商户 A 的 HTTP 请求");
}
@Override
void verifySignature() {
System.out.println("验证商户 A 返回报文签名");
}
// 公司 A 走 http代理
@Override
boolean isRequestByProxy(){
return true;
}
}
商户 B 的实现:
/**
* 商户 B 实现类
*/
class CompanyBServiceImpl extends AbstractMerchantService {
@Override
void queryMerchantInfo() {
System.out.println("查询商户 B 信息");
}
@Override
void signature() {
System.out.println("对商户 B 请求报文加签");
}
@Override
void httpRequest() {
System.out.println("通过直连发送商户 B 的 HTTP 请求");
}
@Override
void verifySignature() {
System.out.println("验证商户 B 返回报文签名");
}
// 公司 B 不走 http代理
@Override
boolean 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 {
// 使用线程安全的List
private 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 {
@Override
public void doEvent() {
System.out.println("发送 IM 消息");
}
}
// 短信观察者
public class MobileNoObserver implements Observer {
@Override
public void doEvent() {
System.out.println("发送短信消息");
}
}
// 邮件观察者
public class EmailObserver implements Observer {
@Override
public 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 {
@Override
public 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;
}
@Override
public 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;
}
@Override
public 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();
}
@Override
public 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 优缺点分析
优点:
- 增强功能: 通过代理对象添加额外功能(如日志、权限检查等)。
- 解耦合: 客户端与实际对象之间的交互通过代理对象实现,便于维护。
- 灵活性: 动态代理可在运行时动态生成代理对象,无需手动编写代理类。
缺点:
- 性能开销: 动态代理涉及反射调用,性能略低于静态代理。
- 复杂性增加: 对新手开发者来说,动态代理的学习曲线较陡。