架构师之路(一)
1.1 场景问题
登录模块
由于客户系统的不断更替现在加了很多种登录 从原来的app 登录和后台登录 现在需要加小程序登录
1.2 解决方案
要实现对不同的登录功能,无外乎就是加接口加新业务,也不多难,很快就有朋友能写出如下的实现代码,示例代码如下:
package com.czh.service; /** * @ClassNAME LoginService * @Description TODO * @Author czh * @Date 2024/5/16 09:28 * @Version 1.0 */ public interface LoginService { /** * 后台登录 * @param userName * @param password */ void login(String userName, String password); /** * 微信小程序登录 * @param token * */ void WxLogin(String token); /** * app登录 * @param phone * */ void AppLogin(String phone); }
package com.czh.service.impl; import com.czh.service.LoginService; import org.springframework.stereotype.Service; /** * @ClassNAME LoginServiceImpl * @Description TODO * @Author czh * @Date 2024/5/16 09:31 * @Version 1.0 */ @Service public class LoginServiceImpl implements LoginService { @Override public void login(String userName, String password) { System.out.println("后台登录成功"); } @Override public void wxLogin(String token) { System.out.println("微信登录成功"); } @Override public void appLogin(String phone) { System.out.println("app登录成功"); } }
package com.czh.controller; import com.czh.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassNAME LoginController * @Description TODO * @Author czh * @Date 2024/5/16 09:32 * @Version 1.0 */ @RestController public class LoginController { @Autowired private LoginService loginService; /** * 后台登录 * @param userName * @param password */ @PostMapping("login") public void login(String userName, String password){ loginService.login(userName,password); } /** * app登录 * @param phone * */ @PostMapping("appLogin") public void appLogin(String phone){ loginService.appLogin(phone); } /** * 后台登录 * @param token * */ @PostMapping("login") public void login(String token){ loginService.wxLogin(token); } }
1.3 有何问题
上面的写法是很简单的,也很容易想,但是仔细想想,这样实现,问题可不小,
比如:第一个问题:登录类包含了所有登录的算法,使得登录类,比较庞杂,难以维护。后续的修改还需要对程序的影响较大。
比如:第二个问题:以后要每新增一个登录业务都需要改这些代码对程序健壮行很差。
1.3 解决方案:使用策略模式(设计模式)
此链接介绍策略模式 策略模式(设计模式)
上代码:
抽取一个接口为登录业务 只定义一个登录方法其中方法参数为可变参数(当然也可以用实体类看情况)
package com.czh.design.login; public interface LoginService { void login(String... params); }
新增一个注解为了不修改中间层代码动态去读取新业务
package com.czh.design.login; import com.czh.enums.LoginEnum; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface LoginType { LoginEnum value(); }
为了更好维护我们使用枚举类分辨不同登录
package com.czh.enums; /** * @ClassNAME LoginEnum * @Description TODO * @Author czh * @Date 2024/5/15 17:01 * @Version 1.0 */ public enum LoginEnum { /** * app登录 */ APP, /** * 后台登录 */ ADMIN; }
新增后台登录类实现登录接口
package com.czh.design.login; import com.czh.enums.LoginEnum; import org.springframework.stereotype.Service; @Service @LoginType(LoginEnum.ADMIN) public class AdminLoginService implements LoginService { @Override public void login(String... params) { System.out.println("后台登录"+"账号"+params[0]+"密码"+params[1]); } }
新增APP登录类实现登录接口(这里app和小程序就写一个就行为了简化演示代码)
package com.czh.design.login; import com.czh.enums.LoginEnum; import org.springframework.stereotype.Service; @Service @LoginType(LoginEnum.APP) public class AppLoginService implements LoginService { @Override public void login(String... params) { System.out.println("小程序登录token====>"+params[0]); } }
新增上下文LoginContext
package com.czh.design.login; /** * @ClassNAME LoginContext * @Description TODO * @Author czh * @Date 2024/5/15 22:26 * @Version 1.0 */ import com.czh.enums.LoginEnum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component public class LoginContext { private final Map<LoginEnum, LoginService> loginServiceMap = new ConcurrentHashMap<>(); private final ApplicationContext applicationContext; @Autowired public LoginContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @PostConstruct public void init() { Map<String, Object> beans = applicationContext.getBeansWithAnnotation(LoginType.class); for (Object bean : beans.values()) { LoginType loginType = bean.getClass().getAnnotation(LoginType.class); // 检查map中是否已存在该类型的服务,并且新的bean不是@Primary时跳过 if (!loginServiceMap.containsKey(loginType.value()) || bean.getClass().isAnnotationPresent(Primary.class)) { loginServiceMap.put(loginType.value(), (LoginService) bean); } } } public void login(LoginEnum type, String... params) { LoginService loginService = loginServiceMap.get(type); if (loginService == null) { throw new IllegalArgumentException("未知登录类型: " + type); } loginService.login(params); } }
当然这里逻辑也很简单先把我们找类上带自定义注解LoginType的类将放到一个map中
其中key是注解value 值是此对象
根据传来的枚举类参数拿到对应的登录接口的实现类
新的控制层
package com.czh.controller; import com.czh.design.login.LoginContext; import com.czh.enums.LoginEnum; import com.czh.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassNAME LoginController * @Description TODO * @Author czh * @Date 2024/5/16 09:32 * @Version 1.0 */ @RestController public class LoginController { @Autowired private LoginContext loginContext; /** * 后台登录 * @param userName * @param password */ @PostMapping("login") public void login(String userName, String password){ loginContext.login(LoginEnum.ADMIN,userName,password); } /** * app登录 * @param token * */ @PostMapping("appLogin") public void appLogin(String token){ loginContext.login(LoginEnum.APP,token); } }
1.4 有何好处
1.每个不同的登录类都实现了单一原则 如果登录出现问题只需要到对应的实现类去修改就行对其他代码没有影响
2.如果要新增一个登录类型,只需要新增一个登录类型实现类实现登录接口其他的不需要修改
比如:新增支付宝小程序登录
新增支付宝枚举类型
package com.czh.enums; /** * @ClassNAME LoginEnum * @Description TODO * @Author czh * @Date 2024/5/15 17:01 * @Version 1.0 */ public enum LoginEnum { /** * app登录 */ APP, /** * 后台登录 */ ADMIN, /** * 支付宝登录 */ ZFB; }新增支付宝业务实现类
package com.czh.design.login; import com.czh.enums.LoginEnum; import org.springframework.stereotype.Service; @Service @LoginType(LoginEnum.ZFB) public class ZFBLoginService implements LoginService { @Override public void login(String... params) { System.out.println("支付宝登录token"+params[0]); } }
控制层只需要新增一个支付宝接口就可以了
package com.czh.controller; import com.czh.aop.annotation.Anonymous; import com.czh.design.login.LoginContext; import com.czh.enums.LoginEnum; import com.czh.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassNAME LoginController * @Description TODO * @Author czh * @Date 2024/5/16 09:32 * @Version 1.0 */ @RestController @Anonymous public class LoginController { @Autowired private LoginContext loginContext; /** * 后台登录 * @param userName * @param password */ @PostMapping("login") public void login(String userName, String password){ loginContext.login(LoginEnum.ADMIN,userName,password); } /** * app登录 * @param token * */ @PostMapping("appLogin") public void appLogin(String token){ loginContext.login(LoginEnum.APP,token); } /** * 支付宝登录 * @param token * */ @PostMapping("zfbLogin") public void zfbLogin(String token){ loginContext.login(LoginEnum.ZFB,token); } }
1.5 二次修改
比如客户要修改app登录现在需要验证码和密码同时才可以
上代码
新增一个app业务二次修改的实现类 继承AppLoginService 覆盖原本的方法就会走覆盖方法
package com.czh.design.login; import com.czh.enums.LoginEnum; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; @Service @Primary @LoginType(LoginEnum.APP) public class AppLoginOneService extends AppLoginService implements LoginService { @Override public void login(String... params) { System.out.println("小程序登录二次修改token====>"+params[0]); } }
此设计模式可以针对多种
1.多支付:微信支付 支付宝支付
2.多策略 比如新用户百分之2优惠 老用户百分之5优惠 大客户百分之10优惠
3.。。。。。。还有很多根据实际情况来做判断
好了到此结束了如果对你有用请点一下赞!