在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
之前的文章有详细介绍: 设计模式之工厂模式
最近在开发的项目是个门户系统,其中有个需求是对接多个外部系统,当门户点击对应的系统做权限认证使得用户点击链接可以直接跳转至系统,不需要登录可以直接使用系统。既然是对接多个外部系统,那么意味着后期可能还会对接更多外部其他系统,既然如此使用策略模式+简单工厂再合适不过了。使用策略模式+简单工厂使其解耦,便于维护,后期可扩展。
public interface OSAuthService {
/**
* 生成鉴权url
*
* @param url 未鉴权url
* @return 鉴权后的url
*/
String generateAuthUrl(OSAuthDTO url);
}
@Component(ServiceNameConst.SHR)
public class ShrAuthServiceImpl implements OSAuthService {
private final static String redirectToConst = "redirectTo";
/**
* 生成鉴权url
*
* @param authDTO 未鉴权url
* @return 鉴权后的url
*/
@Override
public String generateAuthUrl(OSAuthDTO authDTO) {
String url = SSOUtil.generateUrl(new MockHttpServletRequest() {
@Override
public String getParameter(String name) {
if (redirectToConst.equals(name)) {
return authDTO.getUrl();
}
return super.getParameter(name);
}
});
return url;
}
}
@Component(ServiceNameConst.USERNAME)
public class UsernameAuthServiceImpl implements OSAuthService {
/**
* 生成鉴权url
*
* @param url 未鉴权url
* @return 鉴权后的url
*/
@Override
public String generateAuthUrl(OSAuthDTO url) {
return getAuthUrl(url.getUsernameKey(), url.getUrl());
}
/**
* 获取username方式鉴权url
*
* @param key nameKey
* @param uriPrefix url
* @return 鉴权url
*/
private String getAuthUrl(String key, String uriPrefix) {
String userNoEncrypt = AesEncodeUtil.encrypt(ContextUtil.getContext().getUser().getUsername());
try {
userNoEncrypt = URLEncoder.encode(userNoEncrypt, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String uri = null;
if (uriPrefix != null) {
if (uriPrefix.indexOf("?") != -1) {
if (uriPrefix.indexOf("?") != (uriPrefix.length() - 1)) {
uri = uriPrefix + "&" + key + "=" + userNoEncrypt;
} else if (uriPrefix.indexOf("?") == (uriPrefix.length() - 1)) {
uri = uriPrefix + key + "=" + userNoEncrypt;
}
} else {
uri = uriPrefix + "?" + key + "=" + userNoEncrypt;
}
}
return uri;
}
}
public class OSAuthContext {
public OSAuthContext() {
}
public OSAuthContext(OSAuthService osAuthService) {
this.osAuthService = osAuthService;
}
private OSAuthService osAuthService;
public void setOsAuthService(OSAuthService osAuthService) {
this.osAuthService = osAuthService;
}
public String generateAuthUrl(OSAuthDTO authDTO) {
return osAuthService.generateAuthUrl(authDTO);
}
}
@Component
public class OSAuthServiceFactory {
private static Map<String, OSAuthService> serviceMap;
public static OSAuthService createOsAuthService(VisitTypeEnum visitTypeEnum) {
OSAuthService service = null;
switch (visitTypeEnum) {
case SHR:
service = serviceMap.get(ServiceNameConst.SHR);
break;
case UNAME:
service = serviceMap.get(ServiceNameConst.USERNAME);
break;
default:
break;
}
return service;
}
@Autowired
private void setServiceMap(Map<String, OSAuthService> map) {
OSAuthServiceFactory.serviceMap = map;
}
}
public interface ServiceNameConst {
String SHR = "shrAuthService";
String USERNAME = "usernameAuthService";
}
@Data
public class OSAuthDTO {
private String url;
private String usernameKey;
}
根据配置的鉴权类型,在工厂中获取对应的鉴权服务生成鉴权URL。
@Service
@Slf4j
public class UserModuleServiceImpl implements UserModuleService {
//......省略其他代码只留重点代码
@Override
public ResponseData<ConfigEntranceResponse> getConfigEntrance() {
ConfigEntranceResponse configEntrance = new ConfigEntranceResponse();
List<EwSysModule> userEntrance = getUserEntrance();
// 鉴权
if (userEntrance != null) {
initAuthUrl(userEntrance);
}
// 用户自定义入口
configEntrance.setUserEntrance(userEntrance);
List<EwSysModule> otherEntrance = getOtherEntrance();
// 鉴权
if (otherEntrance != null) {
initAuthUrl(otherEntrance);
}
// 用户未设置模块
configEntrance.setOtherEntrance(otherEntrance);
return ResponseData.success(configEntrance);
}
/**
* 初始化鉴权url
*
* @param list
*/
private void initAuthUrl(List<EwSysModule> list) {
OSAuthContext context = new OSAuthContext();
for (EwSysModule module : list) {
VisitTypeEnum visitType = EnumUtil.getEnumByCode(VisitTypeEnum.class, module.getVisitType()).get();
OSAuthService osAuthService = OSAuthServiceFactory.createOsAuthService(visitType);
if (osAuthService != null) {
context.setOsAuthService(osAuthService);
OSAuthDTO osAuthDTO = new OSAuthDTO();
osAuthDTO.setUrl(module.getUrl());
osAuthDTO.setUsernameKey(module.getUsernameKey());
String authUrl = context.generateAuthUrl(osAuthDTO);
module.setUrl(authUrl);
}
}
}
}
开发完一期的需求,果然不出我所料二期需求中需要对接另一个外部系统,这时只需要实现OSAuthService接口实现具体鉴权逻辑即可。而不用在原来代码逻辑中做改动,原来的代码几乎可以不用改动即可完成新的需求,这就是开闭原则。
策略模式+简单工厂模式适合一个行为有多种实现方式的情况下使用。
例如支付:可以使用支付宝、微信、信用卡这种业务场景也可以使用策略模式+简单工厂模式。
这样做的目的是为了解耦,后期可拓展,易维护。
设计模式要结合业务场景使用,而不是硬套设计模式。