一、介绍
在做业务功能开发的时候,每个程序员接触过最多的应该就是登录功能了,而一个登录功能有很多种登录交互的体现,当我们开始写登录代码的时候,前期能满足登录就行了,渐渐的系统中加入了不同业务需求的代码,以及每个登录需要特殊处理的参数,基本上我们要开发不同的接口和判断逻辑,对于经验尚浅的程序员在开发过程中会渐渐的COPY原有能复制的代码,让增加上新的逻辑与分支,而在真实的企业过程中,会经常出现多个人由于不同的原因修改接口,登录也不例外,当经过了N个不同水平的人修改登录功能后,我们会逐渐的发现当前登录的逻辑散落在项目的各个地方,每次找下登录功能的某些逻辑需要定位一段时间,如果有设计文档的情况还能稍微快点,定位到代码后也会看到某些不同场景的判断,东一块分支,西一块分支。最终登录功能的代码的参数越来越长、代码重复越来越高。
为了解决这个问题,我们可以定期的思考优化办法,并通过设计模式统一维护不同的登录实现,本文分享这个内容。
二、策略模式+工厂模式重构
2.1 登录场景
当电商系统项目越来越大的时候,针对不同终端、不同渠道、不同用户我们会产生大量的登录的代码,如果没有抽象出一个易于维护的类结构,那么对于后期的维护和定位也相对耗时。
当一个系统需要支持如下需求的时候:
1、支持PC端、H5端、小程序登录
2、支持用户名密码登录、手机验证码登录、SSO登录
3、支持第三方微信扫码登录、微信小程序登录
4、支持普通用户登录、公司用户登录、内部员工账号登录
前期感觉还不错,后期代码越来越难以维护,想快速定位某个登录代码,也得找一下。
对于这样的非常明显的【不同实现】的场景的时候,我们可以用经典的策略模式来解决问题,对于一个程序员来说,策略模式是使用频率最高的模式,也是很多设计模式的基础,当自己开发某些同类的分支逻辑的时候,使用策略模式就已经成功了一大半,不过前期还是先需要确认是否达到了使用策略模式的阶段,而不是上来就搞个设计模式完成功能,我觉得如果符合了如下场景可能该是时候用了:
1、代码中同类型的分支超过了2个以上,且未来容易存在更多业务的支持需求
2、代码中开发者已经能够识别出来相同的逻辑是什么,不同的逻辑是什么
假设我们现在的需求如下,需要支持如下登录场景:
手机短信验证码登录
:用户输入手机验证码数据后,完成登录
用户名密码登录
:用户输入用户名和密码数据后,完成登录
微信小程序授权Code登录
:用户在微信小程序内部,通过微信授权Code完成登录
PC端微信扫码直接登录
:用户直接通过PC浏览器扫码后完成登录
PC端微信扫码注册登录
:用户未注册的情况下,扫码后自动注册完成登
手机号注册登录
:用户输入手机号注册后完成登录。
H5端微信登录
:用户在H5端微信浏览器环境下进行授权登录
用户统一平台会话自动登录
:用户登录了其他平台,单击统一登录,自动登录,比如登录了百度,自动登录网盘
企业员工登录
:用户是网站公司的员工,使用公司的自己账号登录
第三方平台登录
:用户通过第三方社交平台,实现第三方平台账号的登录。
2.2 矩阵思维分析方法
那么我们可以分析一下,分析出来假设我们要通过设计模式来处理,该怎么做,这里可以使用矩阵思维。
矩阵思维是一种分析和解决问题的思维方式,利用矩阵结构来整理和展示信息,从而揭示问题的多个维度和关系。它常用于复杂的决策或分析场景,有助于理清各类信息,并找出最优解决方案。
矩阵思维的核心是通过表格(通常是二维表)的形式,把信息拆解成不同维度,每个维度代表一组变量或因素。通过行和列的组合,我们可以观察到不同维度之间的关系,帮助我们全方位、多角度地理解问题。
矩阵思维是一种有效的逻辑框架,适合用在数据复杂、维度多样的决策问题中。通过将问题维度化、结构化,它可以帮助我们更全面地理解问题并做出合理决策。
在做矩阵分析的时候,纵轴可以选择使用业务场景,横轴是备选维度,可以是受场景影响的业务流程,也可以是受场景影响的业务属性,或者任何其它不同性质的“东西”。
通过矩阵图,可以清晰地展现不同场景下,业务的差异性。基于此,我们可以定制满足差异性的最佳实现策略,可能是多态扩展,可能是分离的代码,也可能是其它。
接下来我们可以把登录流程分解成这几个关键步骤(请求参数、参数校验、业务处理、模拟会话登录、结果封装),再结合场景,因此我们的矩阵如下:
通过这样的业务梳理我们可以知道,对于多种场景的登录设计,我们最终的目的就是为了创建访问令牌会话,然后返回会话信息。
相同点和不同点分析如下:
相同点
:
1、创建用户会话:需要创建一个新的用户会话。
2、返回结果封装:所有场景返回用户信息和token,便于客户端使用。
不同点
:
1、请求参数:每个场景的请求参数不同,比如手机号、验证码、用户名密码、微信Code、第三方授权信息等。
2、参数校验:校验逻辑随请求参数变化,例如手机号格式校验、验证码有效性校验、Code校验等。
3、业务处理:有些场景需要查找或创建用户(如微信登录和第三方平台登录),有些只需验证用户信息(如用户名密码登录、员工账号登录)。
当我们分析出来这样的一个基本结构后,我们就可以开始尝试写代码了。
1、首先我们创建一个SpringBoot工程结构,然后新建一个登录策略的枚举类,代码如下所示:
2、然后再创建登录策略枚举接口和实现:
然后我们在新建一个BaseLoginStrategy策略类,用于维护多种策略之间相同的代码,同时该类需要实现接口,每个策略类继承该类,这里建议设计为Abstract的抽象类更合适:
设置完成后我们的类图如下所示:
接下来我们再来思考下第一步干什么,登录的第一步就是参数获取,但是我们知道在登录的时候我们每种方式的登录的请求参数不一样,那如何解决呢?
我们需要知道的是Controller层的参数就是不一样的,但是我们的登录的业务处理层的接收参数是可以统一规划为一样的。
只需要创建10个参数类,然后定义个接口就行了。如下所示:
定义一个登录请求的接口或者基础类:
然后再创建10个登录请求参数类:
接下来我们写个工厂类来驱动策略的选择,对于策略模式的代码写法,没有绝对固定的写法,都是根据当前项目和系统的业务场景来选择不同的策略选择的方法,某些场景下依然会用if/else来根据一定的条件进行具体策略的选择,某些场景我们会从IOC容器中获取Spring Bean实例来,某些场景会从SPI扩展口中获取,某些场景下是人工手动注册的方式处理。这里我们暂时先以手动注册的方式进行处理,创建一个类LoginStrategyManualFactory,
代码如下:
这是一个非常典型的工厂模式的使用方法,这里我通过固定创建对象的方式实现了每种策略的初始化,在实际使用的时候,可以更改为从IOC容器中获取。
这种设计的优点
1、解耦策略创建:调用方无需了解策略的具体实现,只需通过工厂获取实例。
2、易于扩展:新增策略只需在工厂注册即可,无需修改业务逻辑。
接下来我们在登录策略接口中写一个方法,用于定义登录操作:
然后再BaseLoginStrategy暂时重写登录请求处理,不用每个子类实现:
然后接下来我们这里模拟下验证码登录登录和用户名密码登录:
验证码登录策略实现如下:
用户名密码登录策略实现如下:
这样一个简单的处理我们就搞定了,接下来我们写个客户端测试一下:
可以看到这里我使用了非常清晰易懂的方式完成了不同登录策略的处理,这样在实际的系统中我们可以通过类似的方式进行代码改造。
这样我们就使用策略设计模式重构了电商系统的登录功能。
三、模板方法设计模式重构
在刚才的分享中,我们使用策略模式隔离了每种登录业务场景的代码实现,第一步重构完成了,接下来我们开始做第二步。
当我们做一个登录功能的时候,常见的一个流程如下:
如果自己能够识别出来每一种登录场景的相同点和不同点,那我们就能够抽象出这样的一个流程,就可以用模板方法模式在升级下当前登录的代码。
首先我们创建一个抽象的模板类,代码如下所示:
然后分别定义几个方法:
这里我定义了3个抽象的方法:
校验数据的方法
执行登录的方法
构造数据结果的方法
这里是需要不同的登录场景进行调用处理。然后父类中统一的处理公共的登录一些行为,一些后置的动作。
有了这个结构后还不够,我们会发现当前缺少一个对象,这个对象需要作为桥梁传递每一个步骤的数据,因此我们在定义一个类:loginDataContext。
有了这个类后,首先更改下接口类,增加上返回值,后续这里可以将context类作为全局传递的参数处理:
我们在更改下抽象类的代码如下:
这样设计后,子类就可以将自己的不同类型的返回结果设置进去,同时如果每一步的产生的结果都可以放到loginDataContext对象中,这个类其实可以继承HashMap对象,这样不同的登录子类就可以直接put自定义的对象。
这里以用户名和密码为例,实现相关的方法。
这种思想也是一种编排式的代码设计思想,即抽象的类负责统一编排业务的流程,然后不同的登录场景负责处理自己的不同的实现,有需要的情况下重载父类的某些方法。
这样我们未来调用的地方就可以写上类似如下的代码:
通过这样的模板方法模式的处理,就可以统一每种登录场景的流程。避免每个开发者写的登录流程散落在项目中任何地方了。
四、添加责任链模式实现校验
在电商系统中,登录校验可能包括以下逻辑:
参数校验(用户名是否为空、密码是否符合规则)
。
黑名单校验(用户是否被封禁)
。
IP 限制(是否来自受限区域或多次失败登录)
。
登录行为校验(如频繁登录或机器人攻击)
。
直接把这些逻辑硬编码在 validateData() 方法里会导致代码复杂且难以维护,且未来扩展变更不灵活。因此可以通过责任链模式将校验逻辑解耦为独立节点,按需组合。这种模式可以根据需求动态调整校验逻辑的顺序或内容,每个校验逻辑可以独立维护,其他场景也可复用。
我们也可以在集成下责任链模式,比如登录过程中的校验往往包含多种逻辑(如参数校验、黑名单校验、IP 限制等)。将这些校验拆分为独立的责任节点,并通过责任链模式动态组合校验逻辑,可以提升灵活性和可复用性。
实现方式可以分成2个步骤:
1、定义一个 LoginValidationHandler 抽象类,每个校验逻辑实现一个具体的 Handler。
2、将所有校验 Handler 按需组合成一条责任链,并在登录前依次执行。
具体操作如下:
(1) 定义责任节点接口
首先,定义一个责任节点的抽象类,提供校验逻辑的框架和节点链接方法:
这段代码的核心思想如下:
每个校验逻辑作为一个节点,单独实现 doHandle 方法。
如果当前校验通过(返回 true),调用链上下一个节点。
如果校验不通过(返回 false 或抛异常),整个链条中断。
(2) 编写具体的校验节点
示例 1
:用户名参数校验
校验用户名是否为空或非法:
示例 2
:黑名单校验
校验用户是否被封禁:
示例 3
:IP 限制校验
校验登录请求来源 IP 是否受限:
(3) 动态组装责任链
创建一条完整的校验链,按顺序调用各节点:
(4) 在策略中使用责任链
在 AbstractLoginStrategy 的 validateData 方法中调用责任链:
这样设计完成后,优点如下:
解耦校验逻辑
:每个校验逻辑作为独立节点实现,易于修改或扩展。
灵活组合
:责任链的顺序和内容可以动态调整,适应不同场景需求。
增强复用
:校验节点可以复用于其他功能模块(如注册、支付验证等)。
遵循开闭原则
:新增校验逻辑时,只需添加新节点并注册到责任链,无需修改现有代码。
如果未来想调整责任链的顺序,可以通过SPI技术或者注解技术实现,例如注解计算如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ValidationOrder {int value(); // 顺序
}
// 添加注解到节点
@ValidationOrder(1)
public class UsernameValidationHandler
extends LoginValidationHandler {}
SPI
的技术实现如下:
// 通过反射加载责任链
public static LoginValidationHandler buildChainDynamic() {Iterator<LoginValidationHandler> iterator = ServiceLoader.load(LoginValidationHandler.class).iterator();List<LoginValidationHandler> handlers = new ArrayList<>();while (iterator.hasNext()) {LoginValidationHandler handler = iterator.next();handlers.add(handler);}handlers = handlers.stream().sorted(Comparator.comparingInt(h -> h.getClass().getAnnotation(ValidationOrder.class).value())).collect(Collectors.toList());for (int i = 0; i < handlers.size() - 1; i++) {handlers.get(i).setNextHandler(handlers.get(i + 1));}return handlers.isEmpty() ? null : handlers.get(0);}
责任链模式在电商登录功能中可以大幅提高系统的灵活性和可维护性,同时满足多场景校验需求。
五、装饰模式实现动态增强
某些登录策略可能需要额外的增强功能(如多因素认证、限流等),而不希望污染核心策略逻辑。可以使用装饰器模式动态扩展策略功能。
实现方式
定义一个 LoginStrategyDecorator 类,包装具体的策略并添加额外逻辑。
允许动态为某些策略增加功能,而不影响其他策略。
样例代码如下:
优点
1、动态增强:为特定策略灵活添加新功能。
2、避免修改:无需改动原有策略实现,符合开闭原则。
六、总结
最后我总结下,基于设计模式重构电商登录功能的价值体现在以下几个方面:
1、职责清晰,提升代码可维护性
使用策略模式将登录场景的不同实现分离到独立的类中,每个类只负责实现特定的登录逻辑(如用户名密码登录、第三方登录、短信验证码登录等)。抽象类 AbstractLoginStrategy 提供了通用的骨架,具体实现只需关注自身逻辑,这种分工使得代码职责更加清晰,易于维护和扩展。
2、增强扩展性,支持未来需求变化
新增登录场景时,只需创建一个新的策略类并实现抽象方法,而无需修改现有的代码。这种“开闭原则” 的实现方式,减少了修改已有代码可能引入的风险。比如未来支持生物识别登录 或微信扫码登录,只需扩展对应策略,而无需大范围改动现有逻辑。
3、复用通用逻辑,减少重复代码
通过在抽象类中定义登录的通用流程(如数据校验、登录成功后的日志记录、会话处理等),避免每个场景实现都重复这些逻辑。具体策略类只需关注核心的差异化逻辑(如 validateData 或 executeLogin),提升代码复用性并降低冗余。
4、 增强可读性,简化复杂业务逻辑
登录功能涉及多个步骤,如前置校验、登录操作、后置处理等。通过将这些步骤明确拆分为抽象方法并按顺序执行,使得流程结构清晰、逻辑紧凑,便于开发者理解和维护。
5、便于测试和调试,降低 Bug 风险
策略模式的分离设计使得每种登录场景可以独立测试,避免了复杂条件分支(如 if-else 或 switch-case)带来的测试难度。例如,测试 UsernamePasswordLoginStrategy 时只需关注用户名密码场景的输入和输出,而无需关心其他策略。
6、利于横向扩展登录相关功能
在 AbstractLoginStrategy 中定义了公共方法(如 executeLoginAction 和 loginSuccessAfterAction),方便扩展通用功能。比如记录用户登录日志、发送消息队列通知等操作,只需修改抽象类的实现即可对所有登录策略生效,增强了功能的一致性。
7、容易结合多种设计模式,增强系统灵活性
工厂模式:配合策略模式的动态选择,根据请求参数动态生成对应的策略实例。通过工厂类根据登录请求类型动态生成策略对象,避免客户端直接依赖具体实现类,提高可扩展性和解耦性
模板方法模式:登录流程中固定的步骤抽象为模板,并允许子类实现可变部分,具体逻辑交由子类实现,确保流程一致性和扩展性。
责任链模式:扩展前置校验或后置处理的灵活性,比如实现多种校验逻辑的动态组合。
装饰器模式:在策略的基础上动态添加功能,比如为不同的登录场景添加安全措施或登录限流。在不修改核心登录逻辑的情况下,通过装饰器动态增强登录策略的功能,满足个性化需求。
观察者模式:在登录成功后,通知其他系统模块(如统计登录日志、更新在线状态、触发积分奖励等)。实现登录后的解耦扩展,多个观察者可以监听登录事件并执行对应的动作。
状态模式:多阶段流程(某些登录场景可能包含多个阶段(如账号验证、二次认证、授权等),可以使用状态模式管理登录过程中的不同状态),也可以结合用户状态(如新用户、老用户、黑名单用户)动态选择不同的登录策略。根据用户状态驱动策略切换,比如新用户可能需要额外的验证步骤,而老用户则简化流程。
建造者模式:为复杂的登录结果(如用户信息、Token 数据、权限配置)构建统一的返回对象。可以提高代码可读性,简化多步骤构造复杂对象的过程,支持灵活扩展。
本文通过这几个设计模式的应用,不仅提升了代码的模块化和可维护性,还能快速响应业务需求变化。
结合这些设计模式,电商登录功能不仅能满足多场景需求,还能应对复杂的业务变化。
通过工厂模式
动态生成策略、使用责任链模式
处理灵活校验、采用模板方法模式
统一流程、利用装饰器模式
增强功能、以及状态模式
应对多阶段流程,不同设计模式之间相辅相成,进一步提升了系统的可维护性、灵活性和扩展性。这种设计理念也能够很好地适应未来电商系统的快速发展需求。
在电商业务中,登录作为用户交互的核心环节,良好的设计可以显著提高系统的稳定性和可扩展性,同时降低后期迭代的开发和维护成本。这种设计模式的价值在实际项目中尤为显著,尤其是面对复杂的业务需求和频繁的功能扩展时,能够从容应对各种挑战。
往期设计模式文章:Java优雅使用4种设计模式+开闭原则生成网站Sitemap
>原创 无处不在的技术