前言
- 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
- 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
- 策略模式是oop中最著名的设计模式之一,是对方法行为的抽象,可以归类为行为设计模式,也是oop中interface经典的应用。其特点简单又实用,是我最喜欢的模式之一。策略模式定义了一个拥有共同行为的算法族,每个算法都被封装起来,可以互相替换,独立于客户端而变化。策略模式本身的实现比较简单,但是结合单例模式+简单工厂模式+注解+反射,可以构造出近乎完善的策略模式,彻底的消除if-else。
- 工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现
大家假设这样一个场景,比如很多类型分别处理不同的业务逻辑,这些类型以后可能会持续增加到很多很多,最简单的办法就是是一直使用if else,这样会是我们的代码维护变得越来发咋,但根据“开-闭原则”的宗旨:对扩展开放,对修改关闭。后面就想到使用工厂模式+策略模式替代ifelse;
业务场景:
我们常见的登录业务,是每个系统都必不可少的
登录:可以分为如下几种登录方式:
1、 账号密码登录
2、 手机号验证码登录
3、 手机号一键登录
4、 微信登录
这时候我们按照常规的开发不周的话就会使用到很多的if else 如下:
@ApiOperation(value = "登录", notes = "用户登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名(手机号)", required = true, paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, paramType = "query"),
@ApiImplicitParam(name = "loginType", value = "登录方式", defaultValue = "password:密码 code:验证码 onekey:手机号一键登陆", required = true, paramType = "query")
})
@PostMapping("/login")
public Result login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "loginType") int loginType) {
if(loginType == 1){
System.out.println("账号密码方式登录!!!");
}else if(loginType == 2){
System.out.println("手机号验证码方式登录!!!");
}if(loginType == 3){
System.out.println("手机号一键登录!!!");
}else {
System.out.println("登录方式错误!!!");
}
return Result.ok();
// return loginService.login(dto.getLoginType(), dto.getUsername(), dto.getPassword(), null);
}
当使用抽象工厂模式+工厂方法模式+策略模式 这些设计模式结合使用的话,就会使我们的代码变得非常简洁,维护起来也非常简单,下面直接上改进实现步骤
具体结构:
1、登录类型自动义注解类
import java.lang.annotation.*;
/**
* 登录类型自动义注解类
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LoginChannel {
/**
* 登录方式 code
* @return 规定的code
*/
String code();
}
2、登录类型code常量类
/**
* 登录方式code常量类
*/
public final class LoginChannelCode {
private LoginChannelCode() {
throw new AssertionError("不能产生实例");
}
/** 密码方式 登录 */
public static final String PASSWORD_LOGIN_CODE = "password";
/** 验证码 登录 */
public static final String VERIFCODE_LOGIN_CODE = "code";
/** 一键登录 */
public static final String ONEKEY_LOGIN_CODE = "onekey";
}
3、LoginService 登录接口入口类
import com.whcloud.wmp.utils.Result;
/**
* 登录接口
*/
public interface LoginService {
/**
* 登录
* @param loginType 登录方式
* @param userName 用户名
* @param password 密码
* @param code 验证码
* @return
*/
Result login(String loginType, String userName, String password, String code);
}
响应结果类:
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Maps;
import com.whcloud.wmp.domain.Enum.ErrorCode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Map;
/**
* @author
*/
@ApiModel(value = "响应结果")
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = -6190689122701100762L;
/**
* 响应编码
*/
@ApiModelProperty(value = "响应编码:0-请求处理成功")
private Integer code = 0;
/**
* 提示消息
*/
@ApiModelProperty(value = "提示消息")
private String message;
/**
* 请求路径
*/
@ApiModelProperty(value = "请求路径")
private String path;
/**
* 响应数据
*/
@ApiModelProperty(value = "响应数据")
private T data;
/**
* http状态码
*/
private Integer httpStatus;
/**
* 附加数据
*/
@ApiModelProperty(value = "附加数据")
private Map<String, Object> extra;
/**
* 响应时间
*/
@ApiModelProperty(value = "响应时间")
private Long timestamp = System.currentTimeMillis();
@JSONField(serialize = false, deserialize = false)
@JsonIgnore
public Integer getHttpStatus() {
return httpStatus;
}
@JSONField(serialize = false, deserialize = false)
@JsonIgnore
public boolean isOk() {
return this.code == ErrorCode.OK.getCode();
}
public static Result ok() {
return new Result().code(ErrorCode.OK.getCode());
}
public static Result failed() {
return new Result().code(ErrorCode.FAIL.getCode());
}
public Result code(Integer code) {
this.code = code;
return this;
}
public Result msg(String message) {
if (message == null) {
message = "";
}
// this.message = i18n(message,message);
this.message = message;
return this;
}
public Result data(T data) {
this.data = data;
return this;
}
public Result path(String path) {
this.path = path;
return this;
}
public Result httpStatus(int httpStatus) {
this.httpStatus = httpStatus;
return this;
}
public Result put(String key, Object value) {
if (this.extra == null) {
this.extra = Maps.newHashMap();
}
this.extra.put(key, value);
return this;
}
}
4、登录接口适配器
import com.whcloud.wmp.utils.Result;
/**
* 登录接口适配器
*/
public abstract class LoginAdapter implements LoginService {
@Override
public Result login(String loginType, String userName, String password, String code) {
return new Result();
}
}
5、登录方式 代理类
import com.whcloud.wmp.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.Map;
/**
* 登录方式 代理类
*/
@Slf4j
public class LoginDelegate extends LoginAdapter {
@Override
public Result login(String loginType, String userName, String password, String code) {
LoginService loginService = getLoginService(loginType);
if(ObjectUtils.isEmpty(loginService)){
return Result.failed().msg("登录方式错误!!!");
}
return loginService.login(loginType, userName, password, code);
}
private Map<String, LoginService> map;
public LoginDelegate(Map<String, LoginService> map) {
log.info("初始化登录方式====>{}种--{}", map.size(), map);
this.map = map;
}
/**
* @param loginType 登录方式
* @return
*/
private LoginService getLoginService(String loginType) {
if (map.containsKey(loginType)) {
return map.get(loginType);
}
return null;
}
}
6、登录方式 工厂类
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.NonNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 登录方式 工厂类
*/
public class LoginFactoryBean implements FactoryBean<LoginService>, InitializingBean {
private LoginService loginService;
@NonNull
private List<LoginService> loginServiceList;
public List<LoginService> getLoginServiceList() {
return loginServiceList;
}
public void setLoginServiceList(@NonNull List<LoginService> loginServiceList) {
this.loginServiceList = loginServiceList;
}
/**
* 初始化方法
* @throws Exception 异常
*/
@Override
public void afterPropertiesSet() throws Exception {
Map<String, LoginService> map = new HashMap<>(loginServiceList.size() << 1);
for (LoginService r : loginServiceList) {
LoginChannel channel = r.getClass().getAnnotation(LoginChannel.class);
if (channel == null) { continue; }
map.put(channel.code(), r);
}
loginService = new LoginDelegate(map);
}
@Override
public LoginService getObject() throws Exception {
return loginService;
}
@Override
public Class<?> getObjectType() {
return loginService.getClass();
}
}
7、登录方式 config配置类(这个是关键)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.List;
/**
* 登录方式 config配置类
*/
@Configuration
public class LoginChannelConfiguration {
@Bean
@Primary
public LoginFactoryBean repayFactoryBean(List<LoginService> list) {
LoginFactoryBean factoryBean = new LoginFactoryBean();
factoryBean.setLoginServiceList(list);
return factoryBean;
}
}
8,、具体的登录方法实现类(这里之写了三种,如有增加只需要增加一个实现类即可,注意的是的类上的自定义注解 @LoginChannel(code = LoginChannelCode.VERIFCODE_LOGIN_CODE) ),
(1)账号密码登录实现类
import com.whcloud.wmp.channel.LoginAdapter;
import com.whcloud.wmp.channel.LoginChannel;
import com.whcloud.wmp.channel.LoginChannelCode;
import com.whcloud.wmp.utils.Result;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@LoginChannel(code = LoginChannelCode.PASSWORD_LOGIN_CODE)
public class PasswordLogin extends LoginAdapter {
@Override
public Result login(String loginType, String userName, String password, String code) {
//用户名登录方式一些参数验证逻辑
if(StringUtils.isEmpty(userName) || StringUtils.isEmpty(password) ){
return Result.failed().msg("用户名密码不能为空!!!");
}
if(!"admin".equals(userName) || !"111111".equals(password) ){
return Result.failed().msg("用户名或密码错误!!!");
}
return Result.ok().msg("密码方式登陆成功!!!");
}
}
(2)手机号验证码登录实现类
import com.whcloud.wmp.channel.LoginAdapter;
import com.whcloud.wmp.channel.LoginChannel;
import com.whcloud.wmp.channel.LoginChannelCode;
import com.whcloud.wmp.utils.Result;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@LoginChannel(code = LoginChannelCode.VERIFCODE_LOGIN_CODE)
public class VerifCodeLogin extends LoginAdapter {
@Override
public Result login(String loginType, String userName, String password, String code) {
//验证码登录方式一些参数验证逻辑
if(StringUtils.isEmpty(userName) || StringUtils.isEmpty(code) ){
return Result.failed().msg("手机号或验证码不能为空!!!");
}
return Result.ok().msg("验证码方式登陆成功!!!");
}
}
(3)手机号一键登录实现类
import com.whcloud.wmp.channel.LoginAdapter;
import com.whcloud.wmp.channel.LoginChannel;
import com.whcloud.wmp.channel.LoginChannelCode;
import com.whcloud.wmp.utils.Result;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@LoginChannel(code = LoginChannelCode.ONEKEY_LOGIN_CODE)
public class OnkeyLogin extends LoginAdapter {
@Override
public Result login(String loginType, String userName, String password, String code) {
//一键登录方式一些参数验证逻辑
if(StringUtils.isEmpty(userName)){
return Result.failed().msg("手机号不能为空!!!");
}
return Result.ok().msg("一键登录方式登陆成功!!!");
}
}
9、Controller接口入口
@Autowired
private LoginService loginService;
@ApiOperation(value = "登录", notes = "用户登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "用户名(手机号)", required = true, paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, paramType = "query"),
@ApiImplicitParam(name = "code", value = "验证码", required = true, paramType = "query"),
@ApiImplicitParam(name = "loginType", value = "登录方式", defaultValue = "password:密码 code:验证码 onekey:手机号一键登陆", required = true, paramType = "query")
})
@PostMapping("/login")
public Result login(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "code") String code,
@RequestParam(value = "loginType") String loginType) {
// if(loginType == 1){
// System.out.println("账号密码方式登录!!!");
// }else if(loginType == 2){
// System.out.println("手机号验证码方式登录!!!");
// }if(loginType == 3){
// System.out.println("手机号一键登录!!!");
// }else {
// System.out.println("登录方式错误!!!");
// }
// return Result.ok();
/** 改进后 **/
Result result = loginService.login(loginType, username, password, code);
return result;
}
10、实现效果