策略模式

Posted 离线de日常

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了策略模式相关的知识,希望对你有一定的参考价值。

什么是策略模式?

策略模式是一种用来封装变化, 让算法的变化不影响使用的客户,并且可以灵活的替换各种算法(概念这种东西太死板了,要理解它,灵活使用它, 而不是背过它 !!)

使用场景

平时我们登录网站的时候可以看到有多种选择, 普通的用户密码登录、微信登录、qq登录等, 这里我们就用策略模式(实际上采用简单工厂也可以)来模拟这种情景。

策略模式实现

  • 注: 这里采用接口实现,有些书籍可能是采用抽象类, 模式要活用、活用、活用
  • 微信登录、qq登录等实体类及接口
public interface LoginWay {
    Boolean login();
}

public class WeChat implements LoginWay {
    
    @Override
    public Boolean login() {
        System.out.println("欢迎使用微信登录, 登录成功");
        return true;
    }
}

public class QQ implements LoginWay {

    @Override
    public Boolean login() {
        System.out.println("欢迎使用qq登录, 登录成功");
        return true;
    }
}
  • 策略模式实现

    public class LoginStategy {
    LoginWay loginWay;
    
    public LoginStategy(LoginWay loginWay) {
        this.loginWay = loginWay;
    }
    
    public Boolean login(){
        return loginWay.login();
    }
    }
  • 策略模式客户端使用

    public class LoginStategyTest {
    
    /**
     * 基础的策略模式需要在客户端中选择策略, 可以结合简单工厂模式将策略封装在策略类中
     * 如果将具体类创建过程封装到LoginStategy, 实际上在客户端只会了解LoginStategy, 相对于单独的简单工厂方法降低了耦合度
     *
     * 扩展一种新的方式:
     *  1. 扩展一个新的登录方式(loginway)
     *  2. UI端增加一个选项
     *  3. 客户端需要增加一个分支(如果采用简单工厂也只是把客户端的逻辑封装在了工厂中, 一样需要增加分支(如果再结合反射, 完美))
     */
    @Test
    public void testLogin() {
    
        //界面选择登录方式
        String loginWay = "wechat";
        LoginStategy loginStategy ;
        if(loginWay.equalsIgnoreCase("wechat")){
            loginStategy = new LoginStategy(new WeChat());
        }else if(loginWay.equalsIgnoreCase("qq")){
            loginStategy = new LoginStategy(new QQ());
        }else if(loginWay.equalsIgnoreCase("github")){
            loginStategy = new LoginStategy(new GitHub());
        }else {
            loginStategy = new LoginStategy(new Normal());
        }
        assertTrue(loginStategy.login());
        //新加一个登录方式, 需要修改客户端的分支条件
    
    }
    }
  • 简单工厂模式

    public class LoginWayFactory {
    
      //如果采用简单工厂实现 登录方式选择, 也只是把客户端的判断放到了后台而已
      // 所以我们可以约定好传入参数的格式, 这样就可以利用反射去掉if switch的判断逻辑
    public LoginWay createLoginWay(String loginWay) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if(loginWay.equalsIgnoreCase("wechat")){
            return new WeChat();
        }else if(loginWay.equalsIgnoreCase("qq")){
            return new QQ();
        }else if(loginWay.equalsIgnoreCase("github")){
            return new GitHub();
        }else {
            return new Normal();
        }
    
        //利用反射 根据传入类型生成实体类
        //  loginWay = "Normal";
        //  Class<?> clazz = Class.forName("com.lx.designpattern.loginway." + loginWay);
        //  return (LoginWay) clazz.newInstance();
    
    }
    }

    简单工厂客户端使用

    public class LoginWayFactoryTest {
    /**
     * 使用简单工厂方式 我们在客户端我们需要了解到俩个类 LoginWayFactory LoginWay 参与到编译中
     *
     */
    @Test
    public void testLogin() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    
        LoginWayFactory factory = new LoginWayFactory();
        //界面选择不同的登录方式
        LoginWay loginWay = factory.createLoginWay("wechat");
        assertTrue(loginWay.login());
        loginWay = factory.createLoginWay("qq");
        assertTrue(loginWay.login());
    
        //新加一个登录方式, 需要修改factory总的分支条件, 需要loginwayfactory参与编译
        loginWay = factory.createLoginWay("githubxx");
        assertTrue(loginWay.login());
    }
    }

    策略模式与简单工厂混合使用

    public LoginStategy(String loginWay) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    //        if(loginWay.equalsIgnoreCase("wechat")){
    //            this.loginWay = new WeChat();
    //        }else if(loginWay.equalsIgnoreCase("qq")){
    //            this.loginWay = new QQ();
    //        }else if(loginWay.equalsIgnoreCase("github")){
    //            this.loginWay = new GitHub();
    //        }else {
    //            this.loginWay = new Normal();
    //        }
        Class<?> clazz = Class.forName("com.lx.designpattern.loginway." + loginWay);
        this.loginWay = (LoginWay) clazz.newInstance();
    }

    测试与简单工厂结合后策略模式

      /**
     * 测试采用策略模式与简单工厂反射结合
     */
    @Test
    public void testLogin2() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    
        //约定好登录方式标识,传入的标识直接就是类名
        String loginWay = "WeChat";
        LoginStategy loginStategy = new LoginStategy(loginWay);
        assertTrue(loginStategy.login());
    
    }

使用策略模式与简单工厂结合的好处:

  • 如果使用最后一种方式, 如果需要扩展一种新的方式,只需要扩展具体的实现类, 并且在ui上约定好返回字符串即可
  • 再次强调, 设计模式是要灵活,混合的使用, 设计模式是思想, 是为了更方便优雅的扩展、封装

代码路径

https://github.com/offline7LY/designpattern.git

以上是关于策略模式的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁(设计模式应用实战)

用于从 cloudkit 检索单列的代码模式/片段

代码片-策略模式+工厂模式

代码片-策略模式+工厂模式

代码片-策略模式+工厂模式

代码片-策略模式+工厂模式