前言
开发有个著名的设计原则:开闭原则,即对扩展开放,对修改关闭。但是实际开发中鲜有人能运用纯熟,少侠在开发中接触的例子就是,大多数人就是if...else...这样难以扩展的条件判断。那么应该如何优雅的精简掉复杂的逻辑判断呢?当然抽象共性是从产品思维角度的优化方案,今天少侠想说的是通过技术手段实现。
业务背景
首先简单介绍一下业务背景,背景很简单,就是有若干渠道源,如阿里巴巴,腾讯等,针对不同的渠道需要有不同的数据处理逻辑,并且渠道来源之后会不断扩展。
初步实现
先建立一个简单的枚举类:
/**
* @author Carson
* @date 2020/8/24 下午3:10
*/
public enum SourceEnum {
/**
* 阿里巴巴
*/
ALIBABA("ALIBABA"),
/**
* 腾讯
*/
TENCENT("TENCENT"),
;
public String name;
SourceEnum(String name) {
this.name = name;
}
//匹配
public static SourceEnum match(String name){
SourceEnum[] values = SourceEnum.values();
for (SourceEnum value : values) {
if(value.name.equals(name)){
return value;
}
}
return null;
}
public String getName() {
return name;
}
}
再来看看业务层接口和具体的实现类(注意实现类上的@Service注解是指定的了别名的,方便之后利用 @Qualifier注解调用):
/**
* @author Carson
* @date 2020/8/24 下午3:10
*/
public interface DataService {
String dataProcess();
}
/**
* @author Carson
* @date 2020/8/24 下午3:14
*/
@Service("alibabaServiceImpl")
public class AlibabaServiceImpl implements DataService {
@Override
public String dataProcess() {
return "Alibaba Process++++++++++++++";
}
}
/**
* @author Carson
* @date 2020/8/24 下午3:12
*/
@Service("tencentServiceImpl")
public class TencentServiceImpl implements DataService {
@Override
public String dataProcess() {
return "Tencent Process++++++++++++++";
}
}
再来看看接口层的实现,主要是让用户传入渠道源source(真实环境中这个可能是从cookies里的用户信息获取,这里且不讨论),然后根据不同的渠道进行判断,注意看代码里的switch...case...语句,如果之后渠道扩展了,这里势必是要做改动的。有人可能觉得,看着还阔以啊,干净整洁,但是之后要是渠道很多呢,万一百八十个渠道,这里看着就很臃肿,并且万一出错排查起来也不方便,说得专业一点,就是扩展性不好。
/**
* @author Carson
* @date 20-8-20 下午5:00
*/
@RestController
public class CommonController {
private final Logger logger = LoggerFactory.getLogger(CommonController.class);
@Qualifier(value = "alibabaServiceImpl")
@Autowired
private AlibabaServiceImpl alibabaServiceImpl;
@Qualifier(value = "tencentServiceImpl")
@Autowired
private TencentServiceImpl tencentServiceImpl;
@GetMapping("/dataHandler")
public String dataHandler(String source) {
if (StringUtils.isBlank(source)) {
return "Empty data";
}
SourceEnum sourceEnum = SourceEnum.match(source);
if (sourceEnum == null) {
return "Empty data";
}
switch (sourceEnum) {
case ALIBABA:
return alibabaServiceImpl.dataProcess();
case TENCENT:
return tencentServiceImpl.dataProcess();
default:
return "Empty data";
}
}
}
代码优化
优化的第一步是从枚举类开始,为每个枚举类指定对应的Service实现类对象,注意,Spring中接口不能被加载成Bean实例,所以需要为属性和方法添加@Lookup
注解)。
@Service
public interface DataService {
// 注意,此处必须加此@Lookup注解,否则容器无法启动
@Lookup
String dataProcess();
}
public class AlibabaServiceImpl implements DataService {
public AlibabaServiceImpl(){}
@Override
public String dataProcess() {
return "Alibaba Process++++++++++++++";
}
}
public class TencentServiceImpl implements DataService {
@Override
public String dataProcess() {
return "Tencent Process++++++++++++++";
}
}
然后是改进后的枚举类:
/**
* @author Carson
* @date 2020/8/24 下午3:10
*/
public enum SourceEnum {
/**
* 阿里巴巴
*/
ALIBABA("ALIBABA", new AlibabaServiceImpl()),
/**
* 腾讯
*/
TENCENT("TENCENT", new TencentServiceImpl()),
;
public String name;
public DataService dataService;
SourceEnum(String name, DataService dataService) {
this.name = name;
this.dataService = dataService;
}
//匹配
public static SourceEnum match(String name) {
SourceEnum[] values = SourceEnum.values();
for (SourceEnum value : values) {
if (value.name.equals(name)) {
return value;
}
}
return null;
}
public String getName() {
return name;
}
public DataService getDataService() {
return dataService;
}
}
然后接口层的改造如下,注意看这里的注解只是配置了接口的自动装配(并未注解所有的实现类),然后通过多态实现具体调用。
@RestController
public class CommonController {
private final Logger logger = LoggerFactory.getLogger(CommonController.class);
@Autowired
private DataService dataService;
@GetMapping("/dataHandler")
public String dataHandler(String source) {
if (StringUtils.isBlank(source)) {
return "Empty data";
}
SourceEnum sourceEnum = SourceEnum.match(source);
if (sourceEnum == null) {
return "Empty data";
}
AbstractDataService dataService = sourceEnum.dataService;
if (dataService == null) {
return "Empty data";
}
return dataService.dataProcess();
}
}
小结
通过使用枚举类,在枚举中将 属性与规则具体实现进行绑定。通过改变可以减少if -else使得代码更加优雅 如果需要新增渠道,我们只需要在编写具体规则实现类时实现DataService接口,并在枚举类中新增的枚举,而不需要改动到原先的任何代码。这符合了开闭原则。
由于Spring默认是单例模式,所以正常开发一个接口有多个实现类都是一个一个指定(@Qualifier
)装配,但是如果想利用多态,让程序决定由哪个具体实现类执行,可以将@Service
注解配置在接口上,同时为接口属性/方法添加@Lookup
注解。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。