看了架构师的代码,切身感受到了代码可扩展性高的魅力

588 阅读4分钟

前言

开发有个著名的设计原则:开闭原则,即对扩展开放,对修改关闭。但是实际开发中鲜有人能运用纯熟,少侠在开发中接触的例子就是,大多数人就是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注解。

点点关注,不会迷路