策略模式的思想不难理解,在Java里就是利用继承和多态,从而实现同一行为在不同场景下有不同的实现。
废话不多说,从最近的一个例子入手。
最近有一个功能,是发送短信验证码接口【SendVerifyCodeService】,只接入了平台A,最初代码如下:
public class SendVerifyCodeServiceImpl implements SendVerifyCodeService {
/**
* 短信平台账户
*/
@Value("${sms_account}")
private String smsAccount;
/**
* 短信平台密钥
*/
@Value("${sms_secret}")
private String smsSecret;
/**
* 发送模板Id
*/
@Value("${sms_templateId}")
private String smsTemplateId;
@Override
public boolean sendVerifyCode(String mobile) throws Exception{
//省略业务判断
SmsSendDto smsSendDto = new SmsSendDto();
smsSendDto.setMobile(mobile);
smsSendDto.setAccount(smsAccount);
smsSendDto.setSecret(smsSecret);
smsSendDto.setSmsTempId(smsTemplateId);
smsSendDto.setContent("短信内容");
return SmsUtil.sendSms(smsSendDto);
}
}
问题:刚做完不久,又接入了另一个平台B,但是平台B的短信参数和平台A不一样,尤其是模板Id是不同的。
常规解决方式:在发送逻辑内加一层判断,针对不同平台取不同的参数。
public class SendVerifyCodeServiceImpl implements SendVerifyCodeService {
/**
* platform_A短信平台账户
*/
@Value("${sms_platform_A_account}")
private String sms_platform_A_account;
/**
* platform_A短信平台密钥
*/
@Value("${sms_platform_A_secret}")
private String sms_platform_A_secret;
/**
* platform_A发送模板Id
*/
@Value("${sms_platform_A_templateId}")
private String sms_platform_A_templateId;
/**
* platform_B短信平台账户
*/
@Value("${sms_platform_B_account}")
private String sms_platform_B_account;
/**
* platform_B短信平台密钥
*/
@Value("${sms_platform_B_secret}")
private String sms_platform_B_secret;
/**
* platform_B发送模板Id
*/
@Value("${sms_platform_B_templateId}")
private String sms_platform_B_templateId;
@Override
public boolean sendVerifyCode(String mobile,Integer type) throws Exception{
//省略业务判断
SmsSendDto smsSendDto = new SmsSendDto();
if(type == 1){
//平台A
smsSendDto.setMobile(mobile);
smsSendDto.setAccount(sms_platform_A_account);
smsSendDto.setSecret(sms_platform_A_secret);
smsSendDto.setSmsTempId(sms_platform_A_templateId);
smsSendDto.setContent("platform_A_短信内容");
}else if(type == 2){
//平台B
smsSendDto.setMobile(mobile);
smsSendDto.setAccount(sms_platform_B_account);
smsSendDto.setSecret(sms_platform_B_secret);
smsSendDto.setSmsTempId(sms_platform_B_templateId);
smsSendDto.setContent("platform_B_短信内容");
}
return SmsUtil.sendSms(smsSendDto);
}
}
但是这样代码很丑也很臭,而且也违反了开闭原则,更致命的是短信发送需要重新测试每个平台,这样分支多了以后也不好维护。怎么办?这时候使用策略模式效果更好。
思考一下:我们现在有一个发送验证码接口【SendVerifyCodeService】,这个对外提供统一服务,但是在具体发送短信时需要区分不同平台以及考虑到不同平台可能带来的不同业务逻辑。所以我们可以新增出一个SendSmsService。
public interface SmsService {
/**
* 发送短信
* @author wayne
* @date 2019/8/28 13:54
* @param
* @return
*/
boolean sendSms(SmsSendDto smsSendDto);
}
这个时候由于平台不同,我们为【SendSmsService】提供两个不同的实现类:
SmsPlatformAServiceImpl
@Service
public class SmsPlatformAServiceImpl implements SmsService {
/**
* platform_A短信平台账户
*/
@Value("${sms_platform_A_account}")
private String sms_platform_A_account;
/**
* platform_A短信平台密钥
*/
@Value("${sms_platform_A_secret}")
private String sms_platform_A_secret;
/**
* platform_A发送模板Id
*/
@Value("${sms_platform_A_templateId}")
private String sms_platform_A_templateId;
@Override
public boolean sendSms(SmsSendDto smsSendDto) {
if(null == smsSendDto) {
return false;
}
smsSendDto.setAccount(sms_platform_A_account);
smsSendDto.setSecret(sms_platform_A_secret);
smsSendDto.setSmsTempId(sms_platform_A_templateId);
// 发送短信验证码
return SmsUtil.sendSms(smsSendDto);
}
}
SmsPlatformBServiceImpl
@Service
public class SmsPlatformBServiceImpl implements SmsService {
/**
* platform_B短信平台账户
*/
@Value("${sms_platform_B_account}")
private String sms_platform_B_account;
/**
* platform_B短信平台密钥
*/
@Value("${sms_platform_B_secret}")
private String sms_platform_B_secret;
/**
* platform_B发送模板Id
*/
@Value("${sms_platform_B_templateId}")
private String sms_platform_B_templateId;
@Override
public boolean sendSms(SmsSendDto smsSendDto) {
if(null == smsSendDto) {
return false;
}
smsSendDto.setAccount(sms_platform_B_account);
smsSendDto.setSecret(sms_platform_B_secret);
smsSendDto.setSmsTempId(sms_platform_B_templateId);
// 发送短信验证码
return SmsUtil.sendSms(smsSendDto);
}
}
这个时候无论新增多少平台,我只要新增一个实现类去配置这个平台对应的业务参数就行,无需改动以前的代码。
但是问题又出现了,怎么去对应呢,我怎么知道此时该去调用哪一个服务呢?这个时候上下文就起作用了,上下文里可以提供出对应关系。
【SmsServiceFactory】的作用就是提供这样一个映射关系,或者说是上下文。这样,我就能根据名称得到对应的Bean。进行后续调用了。
@Service
public class SmsServiceFactory{
@Autowired
Map<String, SmsService> smsServiceMap = new ConcurrentHashMap<>();
public SmsService getSmsService(String serviceName) {
return smsServiceMap.get(serviceName);
}
}
最后我们可以再提供出一个枚举,进行平台和对应Service的映射关系。
public enum SmsServiceType {
SERVICE_A(1,"smsPlatformAServiceImpl"),
SERVICE_B(2,"smsPlatformBServiceImpl");
private Integer source;
private String beanName;
public Integer getSource() {
return source;
}
public void setSource(Integer source) {
this.source = source;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public static String getBeanName(Integer source) {
for(SmsServiceType smsServiceType : SmsServiceType.values()) {
if(smsServiceType.source.equals(source)) {
return smsServiceType.beanName;
}
}
return "";
}
}
准备完毕,最终我们的调用方式就是下面的代码,搞定!!!
public class SendVerifyCodeServiceImpl implements SendVerifyCodeService {
@Autowired
SmsServiceFactory smsServiceFactory;
@Override
public boolean sendVerifyCode(String mobile,Integer type) throws Exception{
SmsService service = smsServiceFactory.getSmsService(SmsServiceType.getBeanName(type));
SmsSendDto smsSendDto = new SmsSendDto();
smsSendDto.setMobile(mobile);
smsSendDto.setContent("发送内容");
service.sendSms(smsSendDto);
}
}