前言

  • 设计模式(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);
    }

当使用抽象工厂模式+工厂方法模式+策略模式 这些设计模式结合使用的话,就会使我们的代码变得非常简洁,维护起来也非常简单,下面直接上改进实现步骤

具体结构: 

java策略模式demo java策略模式和工厂模式_设计模式

 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、实现效果

java策略模式demo java策略模式和工厂模式_策略模式_02

java策略模式demo java策略模式和工厂模式_java_03