策略模式
什么是策略模式?
策略模式是一种用来封装变化, 让算法的变化不影响使用的客户,并且可以灵活的替换各种算法(概念这种东西太死板了,要理解它,灵活使用它, 而不是背过它 !!)
使用场景
- 平时我们登录网站的时候可以看到有多种选择, 普通的用户密码登录、微信登录、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/tree/master/strategy