本文将介绍如何重构项目中的重复查询条件。提高代码的可读性和可维护性。先来看一段代码,这段代码主要就是用于查询电商系统中跟订单有关的信息:
public class Order {
public static final int PAY_TYPE_WECHAT = 1;
public static final int PAY_TYPE_ALIPAY = 2;
public static final int PAY_TYPE_UNIONPAY = 3;
//实际支付金额
private int payValue;
//实际支付时间
private long payTime;
//实际支付类型 1 微信支付 2支付宝支付 3银联支付
private int type;
//支付过程是否使用了贷款
private boolean useLoan;
//下面省略一堆get set方法
}
复制代码
然后有一个订单结果处理系统:
public class OrderFinder {
private List<Order> orders = new ArrayList<>();
//新增一笔订单
public void addOrder(Order order) {
orders.add(order);
}
//返回没有使用过贷款的订单
public List<Order> getNoUseLoan() {
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (!order.isUseLoan()) {
orders.add(order);
}
}
return orders;
}
//返回在这个日期之前的订单
public List<Order> beforDate(long date) {
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (order.getPayTime() < date) {
orders.add(order);
}
}
return orders;
}
//返回在这个日期之前 且金额大于固定值的 的订单
public List<Order> beforDateAndOverValue(long date, int payValue) {
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (order.getPayTime() < date && order.getPayValue() > payValue) {
orders.add(order);
}
}
return orders;
}
//返回大于这个支付金额 并且是使用微信支付的订单
public List<Order> overPayAndUseWechat(int payValue) {
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (order.getPayValue() > payValue && order.getType() == PAY_TYPE_WECHAT) {
orders.add(order);
}
}
return orders;
}
}
复制代码
上面这段代码我想很多人在一些老项目的老类里面应该都会遇到,我们来归纳总结一下上述代码的缺点:
- 重复代码过多,可以看一下暴露出来的public 的查询方法 里面重复的代码太多了,以后维护起来很费劲,很多地方都要改。
- 如果来一个新的需求,需要新增一个新的条件,我们又要重写一堆重复的代码,比如对于上述的系统,我要求提供一个返回在某个日期之前并且是使用微信支付的订单。
- 函数体内部的语句过于僵硬,当系统稍微复杂一点的时候很难一眼看出具体的查询条件是如何,我们这个是demo所以函数名称写的还算清晰,但是当查询条件复杂或者老系统代码写的不好的时候,往往通过函数名是无法得知具体查询条件的,这个时候要经常读函数源码才知道要干什么。
为了解决上述的痛点,我们来做一番重构,这个重构我们要解决的痛点如下:
- 消除上述代码中的重复代码
- 新增需求大部分可以通过组合的形式来写,无需新增查询语句
- 代码的可读性要好,易于维护
我们首先针对上述的需求,来对这些查询条件进行一次归拢:
//查询的基类
public abstract class Spec {
public abstract boolean isFitByCondition(Order order);
}
//早于这个时间的
public class BeforeDateSpec extends Spec {
private long date;
public BeforeDateSpec(long date) {
this.date = date;
}
@Override
public boolean isFitByCondition(Order order) {
return order.getPayTime() < date;
}
}
//不使用贷款的
public class NoLoanSpec extends Spec {
@Override
public boolean isFitByCondition(Order order) {
return !order.isUseLoan();
}
}
//超过一定金额的
public class OverPaySpec extends Spec {
public OverPaySpec(int value) {
this.value = value;
}
private int value;
@Override
public boolean isFitByCondition(Order order) {
return order.getPayValue() > value;
}
}
//使用微信支付的
public class UseWechatSpec extends Spec {
@Override
public boolean isFitByCondition(Order order) {
return order.getType() == Order.PAY_TYPE_WECHAT;
}
}
//组合查询
public class AndSpec extends Spec {
public AndSpec(Spec augEndSpec, Spec addEndSpec) {
this.augEndSpec = augEndSpec;
this.addEndSpec = addEndSpec;
}
private Spec augEndSpec;
private Spec addEndSpec;
@Override
public boolean isFitByCondition(Order order) {
return augEndSpec.isFitByCondition(order) && addEndSpec.isFitByCondition(order);
}
}
复制代码
有了这些查询条件,我们来看看,主类就可以改成:
public class OrderFinder2 {
private List<Order> orders = new ArrayList<>();
//新增一笔订单
public void addOrder(Order order) {
orders.add(order);
}
//返回没有使用过贷款的订单
public List<Order> getNoUseLoan() {
Spec spec = new NoLoanSpec();
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (spec.isFitByCondition(order)) {
orders.add(order);
}
}
return orders;
}
//返回在这个日期之前的订单
public List<Order> beforDate(long date) {
Spec spec = new BeforeDateSpec(date);
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (spec.isFitByCondition(order)) {
orders.add(order);
}
}
return orders;
}
//返回在这个日期之前 且金额大于固定值的 的订单
public List<Order> beforDateAndOverValue(long date, int payValue) {
Spec spec1 = new BeforeDateSpec(date);
Spec spec2 = new OverPaySpec(payValue);
AndSpec andSpec = new AndSpec(spec1, spec2);
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (andSpec.isFitByCondition(order)) {
orders.add(order);
}
}
return orders;
}
public List<Order> overPayAndUseWechat(int payValue) {
Spec spec1 = new UseWechatSpec();
Spec spec2 = new OverPaySpec(payValue);
AndSpec andSpec = new AndSpec(spec1, spec2);
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (andSpec.isFitByCondition(order)) {
orders.add(order);
}
}
return orders;
}
}
复制代码
看看上述的第二版改动里面,我们基本完成了可读性和热插拔的特点,新增的查询以后,只要用spec 组合起来就可以,无需再重复写查询条件,但是这样做显然还不够完美,我们还有那么多的迭代器之类的重复代码, 继续重构:
public class OrderFinder3 {
private List<Order> orders = new ArrayList<>();
//新增一笔订单
public void addOrder(Order order) {
orders.add(order);
}
//注意这个函数是private的
private List<Order> selectBy(Spec spec) {
List<Order> orders = new ArrayList<>();
Iterator iterator = orders.iterator();
while (iterator.hasNext()) {
Order order = (Order) iterator.next();
if (spec.isFitByCondition(order)) {
orders.add(order);
}
}
return orders;
}
//返回没有使用过贷款的订单
public List<Order> getNoUseLoan() {
return selectBy(new NoLoanSpec());
}
//返回在这个日期之前的订单
public List<Order> beforDate(long date) {
return selectBy(new BeforeDateSpec(date));
}
//返回在这个日期之前 且金额大于固定值的 的订单
public List<Order> beforDateAndOverValue(long date, int payValue) {
return selectBy(new AndSpec(new BeforeDateSpec(date), new OverPaySpec(payValue)));
}
public List<Order> overPayAndUseWechat(int payValue) {
return selectBy(new AndSpec(new UseWechatSpec(), new OverPaySpec(payValue)));
}
}
复制代码
到这里,我们就基本完成了重构。 且这样的改动本质上来说也不算很大(但是效果还是不错的),不用担心改动大了以后,测试范围影响广, 在一个成熟的系统里面coding,不要总想着用各种设计模式去优化一些很大的模块,而是应该从小范围的类或者函数入口,持续重构一个个小模块,达到最终优化整个系统的目的。
对于本文来说,实际应用的场景大可不必拘泥在“查询” 这种业务场景上,只要你发现你的类对外提供的众多功能函数里面有大多数重复的代码,和组合起来的条件,那么都可以利用这种写法进行重构。