举个例子,短信发送业务的实现,一般公司会接入多个短信供应商,比如梦网、玄武、阿里云等多个短信平台(我们称之为短信渠道),可能需要针对不同的短信类型或者短信平台的稳定性来切换短信渠道:
1.比如阿里云短信管控很严,带营销字样的短信不让发送,则营销类短信需要使用其他短信渠道来发送;
2.也有可能某个短信平台服务挂了暂时不可用,需要切换到另一个短信渠道;
3.某些短信平台有优惠,则需要临时切换到该短信渠道发送短信;
4.…
上面的业务场景简单来说就是:针对不同的短信渠道来调用对应的短信平台接口实现短信发送。
代码实现如下(注意下面所有的代码都不能直接运行,只是关键逻辑部分的示例代码):
我们有一个短信发送类:SmsSendService,里面有一个send方法发送短信
SmsSendService.java
public class SmsSendService{ /** * @Param phoneNo 手机号 * @Param content 短信内容 */ public void send(String phoneNo,String content){ //从配置中读取 短信渠道 String channelType=config.getChannelType(); //如果是短信渠道A,则调用渠道A的api发送 if(Objects.equals(channelType,"CHANNEL_A")){ System.out.println("通过短信渠道A发送短信"); } //如果是短信渠道B,则调用渠道B的api发送 else if(Objects.equals(channelType,"CHANNEL_B")){ System.out.println("通过短信渠道B发送短信"); } } }
如果某天增加了一个短信渠道C,那么接着追加一个”else if…"
//... 此处省略部分代码 ... //从配置中读取 短信渠道 String channelType=config.getChannelType(); //如果是短信渠道A,则调用渠道A的api发送 if(Objects.equals(channelType,"CHANNEL_A")){ System.out.println("通过短信渠道A发送短信"); } //如果是短信渠道B,则调用渠道B的api发送 else if(Objects.equals(channelType,"CHANNEL_B")){ System.out.println("通过短信渠道B发送短信"); } //ADD: 如果是短信渠道C,则调用渠道C的api发送 else if(Objects.equals(channelType,"CHANNEL_C")){ System.out.println("通过短信渠道C发送短信"); } //... 此处省略部分代码 ...
如果又加其他短信渠道了呢?你又写一个“else if …" ?
显然这种做法不可取,也不符合SOLID原则中的”开闭原则“ ——对扩展开放,对更改封闭。
这样我们每次都需要修改原有代码(对更改没有封闭),不断的添加”if else"。
接下来我们把代码优化一下:
定义一个短信渠道的接口 SmsChannelService,所有的短信渠道API都实现该接口;
短信渠道接口 SmsChannelService.java
public interface SmsChannelService{ //发送短信 void send(String phoneNo,String content); }
短信渠道A SmsChannelServiceImplA.java
public class SmsChannelServiceImplA implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道A发送短信"); } }
短信渠道B SmsChannelServiceImplB.java
public class SmsChannelServiceImplB implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道B发送短信"); } }
通过工厂类来初始化所有短信渠道service
SmsChannelFactory.java
public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; //初始化工厂,将所有的短信渠道Service放入Map中 public SmsChannelFactory(){ //渠道类型为 key , 对应的服务类为value : serviceMap=new HashMap<String, SmsChannelService>(2); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA()); serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); } //根据短信渠道类型获得对应渠道的Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
在原来的SmsSendService中调用不同短信渠道的接口。
原来的 SmsSendService 类优化如下
public class SmsSendService { private SmsChannelFactory smsChannelFactory; public SmsSendService(){ smsChannelFactory=new SmsChannelFactory(); } public void send(String phoneNo,String content){ //从配置中读取 短信渠道 String channelType=config.getChannelType(); //获取渠道类型对应的服务类 SmsChannelService channelService=smsChannelFactory.buildService(channelType); //发送短信 channelService.send(phoneNo,content); } }
这样SmsSendService类非常简洁,把“if else"干掉了,
如果我要增加一个短信渠道C,无需再次更改 SmsSendService 类。
只需要增加一个类 SmsChannelServiceImplC 实现 SmsChannelService 接口,
然后在工厂类 SmsChannelFactory 中增加一行初始化 SmsChannelServiceImplC 的代码即可。
增加短信渠道C的实现 SmsChannelServiceImplC.java
public class SmsChannelServiceImplC implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道C发送短信"); } }
修改工厂类 SmsChannelFactory.java
public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; //初始化 serviceMap ,将所有的短信渠道Service放入Map中 public SmsChannelFactory(){ //渠道类型为 key , 对应的服务类为value : serviceMap=new HashMap<String, SmsChannelService>(3); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA()); serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); //ADD 增加一行 SmsChannelServiceImplC 的初始化代码 serviceMap.put("CHANNEL_C",new SmsChannelServiceImplC()); } //根据渠道类型构建短信渠道Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
“if else"是干掉了,但还是得修改原来的类 SmsChannelFactory ,不满足"开闭原则",有没有更好得方式呢?
我们通过使用spring的依赖注入进一步优化代码:
SmsChannelService 接口增加 getChannelType() 方法,这一步很关键。
public interface SmsChannelService { //发送短信 void send(String phoneNo,String content); //关键:增加getChannelType()方法,子类实现这个方法用于标识出渠道类型 String getChannelType(); }
子类增加该方法的实现,并加上 @Service 注解,使其让spring容器管理起来
SmsChannelServiceImplA.java
@Service public class SmsChannelServiceImplA implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道A发送短信"); } //关键:增加 getChannelType() 实现 public String getChannelType() { return "CHANNEL_A"; } }
SmsChannelServiceImplB.java
@Service public class SmsChannelServiceImplB implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道B发送短信"); } //关键:增加 getChannelType() 实现 public String getChannelType() { return "CHANNEL_B"; } }
修改 SmsChannelFactory 类: 这一步也很关键。
SmsChannelFactory.java
@Service public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; /*注入:通过spring容器将所有实现 SmsChannelService 接口的类的实例注入到 serviceList 中*/ @Autowired private List<SmsChannelService> serviceList; /*通过 @PostConstruct 注解,在 SmsChannelFactory 实例化后,来初始化 serviceMap */ @PostConstruct private void init(){ if(CollectionUtils.isEmpty(serviceList)){ return ; } serviceMap=new HashMap<String, SmsChannelService>(serviceList.size()); //将 serviceList 转换为 serviceMap for (SmsChannelService channelService : serviceList) { String channelType=channelService.getChannelType(); //重复性校验,避免不同实现类的 getChannelType() 方法返回同一个值。 if(serviceMap.get(channelType)!=null){ throw new RuntimeException("同一个短信渠道只能有一个实现类"); } /*渠道类型为 key , 对应的服务类为value : 与“优化代码1”中的通过手工设置“CHANNEL_A"、"CHANNEL_B"相比, 这种方式更加自动化,后续在增加“CHANNEL_C"无需再改此处代码*/ serviceMap.put(channelType,channelService); } } //根据渠道类型获取对应短信渠道的Service public SmsChannelService buildService(String channelType){ return serviceMap.get(channelType); } }
SmsSendService 加上 @Service 注解。通过 @Autowired 注入 SmsChannelFactory
SmsSendService.java
@Service public class SmsSendService { @Autowired private SmsChannelFactory smsChannelFactory; public void send(String phoneNo,String content){ //从配置中读取短信渠道类型 String channelType=config.getChannelType(); //构建渠道类型对应的服务类 SmsChannelService channelService=smsChannelFactory.buildService(channelType); //发送短信 channelService.send(phoneNo,content); } }
这时,如果需要添加一个渠道C,那真的只需要添加一个 SmsChannelServiceImplC 即可,再也不用改原有代码,完全遵循“开闭原则”。
SmsChannelServiceImplC.java
@Service public class SmsChannelServiceImplC implements SmsChannelService { public void send(String phoneNo, String content) { System.out.println("通过短信渠道C发送短信"); } public String getChannelType() { return "CHANNEL_C"; } }
总结
通过上述优化很好的去掉了 “if else" ,再也不会出现”又臭又长“像”卫生卷纸"一样的代码了,而且完全遵循”开闭原则"。
spring是个好东西,关键看你怎么用。