什么是策略模式?
策略模式是一种用来封装变化, 让算法的变化不影响使用的客户,并且可以灵活的替换各种算法(概念这种东西太死板了,要理解它,灵活使用它, 而不是背过它 !!)
使用场景
平时我们登录网站的时候可以看到有多种选择, 普通的用户密码登录、微信登录、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上约定好返回字符串即可
- 再次强调, 设计模式是要灵活,混合的使用, 设计模式是思想, 是为了更方便优雅的扩展、封装