策略模式
定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
将共同的行为封装成策略接口,不同策略实现类只需要实现策略接口编写自己的业务代码,通过上下文获取策略类(多态机制)
角色
上下文(Context)
上下文角色主要作用于操作策略方法,屏蔽了高层模块对策略的直接访问。
抽象策略(Strategy)
对共有的行为进行封装。
具体策略(ConcreteStrategy)
实现抽象策略的具体操作。
优点
避免多重条件判断语句,多重判断不易于维护管理,而且判断和行为逻辑混用。
缺点
客户端(调用者)必须要知道所有的策略类,并且自定决定用哪一个策略类,策略模式只适用于客户端知情的情况。
应用场景
1、多个类只在行为上有稍微不同的场景。
2、策略自由切换。
3、聚合登录、聚合支付。
实例代码
是否看过这样的代码?(伪代码)
public Map login(String loginType){
if(loginType.equals("QQ")){
System.out.print("QQ登录");
}else if(loginType.equals("WECHAT")){
System.out.print("微信登录");
}else if(loginType.equals("ali")){
System.out.print("支付宝登录");
}
}
实际上我们采用策略模式可以简化很多。
public Map login(String loginType){
StrategyContext.login(loginType);
}
今天项目可能要接入QQ第三方登录,我们需要编写QQ登录的业务逻辑,老板哪天开心了又要接入微信登录这时候你又写了个微信的类,可有一天QQ不想支持登录了那你又得修改代码重新编译。
为了应付老板这种多变的需求这时候设计模式就出现啦,策略模式你值得拥有,上代码!
通过策略模式的三个角色我们可以发现,首先我们需要找出共同行为封装成一个接口策略类(Strategy)。
/**
* 这里采用伪代码的形式封装了一个登陆的共同行为。
*/
public interface LoginStrategy {
/**
* 登录
*/
void login();
}
策略的具体实现类伪代码两个类(QQ、WECHAT)。
public class QQLoginStrategy implements LoginStrategy {
@Override
public void login() {
System.out.println("QQ登录");
}
}
public class WechatLoginStrategy implements LoginStrategy {
@Override
public void login() {
System.out.println("微信登录");
}
}
这里采用工厂类来初始化对象。
public class LoginStrategyFactory {
/**
* 采用反射机制获取Class,所以需要有地方维护classpath(枚举)
*
* @param strategyType
* @return
*/
public static LoginStrategy getLoginStrategy(String strategyType){
try {
String classPath = LoginEnum.valueOf(strategyType).getClassPath();
return (LoginStrategy) Class.forName(classPath).newInstance();
} catch (Exception e) {
return null;
}
}
}
维护策略类classpath。
public enum LoginEnum {
QQ("com.animo.strategy.impl.QQLoginStrategy"),
WECHAT("com.animo.strategy.impl.WechatLoginStrategy");
private String classPath;
LoginEnum(String classPath) {
this.classPath = classPath;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
}
上下文具体实现策略。
public class LoginContextStrategy {
/**
* 这里按具体业务返回 这边就直接void了
* @param strategyType
*/
public void login(String strategyType){
if(strategyType==null || ("").equals(strategyType)){
System.out.println("strategyType不能为空");
return;
}
LoginStrategy loginStrategy = LoginStrategyFactory.getLoginStrategy(strategyType);
if(loginStrategy==null){
System.out.println("没有找到具体实现");
return;
}
loginStrategy.login();
}
}
以上代码包含包含四个角色比策略模式所说的三个角色多了一个工厂类,其工厂类的作用就是多态的调用实现类。
不过这代码不适用于Web开发,可以看得出只要每一次访问工厂类中的策略类都会重新创建,只需要稍微改造一下即可。
我们首先需要在策略类上面加上@Component注解使其让Spring管理(单例)性能提升。
随后工厂类肯定就不需要啦,通过Spring管理肯定就是通过ApplicationContext上下文进行获取。
通过Spring获取的话也不需要classpath了,采用的是类首字母小写的形式获取。
在之前通过枚举类去维护classpath,转变成了数据维护。
当然转成数据库维护就要用到mybatis啦具体查询代码就不展示。
数据库表设计
自增ID
策略描述(QQ登录、微信登录)
策略Type(QQ、WECHAT)
BeanId
status(开关,可以用于控制通道是否开启)
策略实现类
头部加入@Component让Spring管理
上下文。
public class LoginContextStrategy {
private Mapper mapper;
/**
* 这里按具体业务返回 这边就直接void了
* @param strategyType
*/
public void login(String strategyType){
if(strategyType==null || ("").equals(strategyType)){
System.out.println("strategyType不能为空");
return;
}
/**
* 1、 通过数据查询具体的策略实现Bean(判断是否有该渠道)
*
* 2、获取BeanId(数据库存在渠道但是没有配置该字段)
*
* 通过SpringUtils获取Bean
*
*/
Entity entity = mapper.getChannel(strategyType);
if(entity == null ){
// 没有找到具体的策略实现类
}
String beanId = entity.getBeanId();
if(//判断是否有BeanId){
// 没有配置 BeanId
}
//还可以在判断是否关闭了某个登录策略
// SpringUtils这个工具类需自己编写
LoginStrategy loginStrategy = SpringUtils.getBean(beanId,LoginStrategy.class);
// loginStrategy 会采用多态的机制去调用对应的实现类方法
loginStrategy.login();
}
}
在项中我们只需要传入策略类型即可,例如我们采用聚合登录的例子来展示了策略模式,则Controller中就有这个一个方法。
public Map login(String loginType){
LoginContextStrategy.login(loginType);
}