如何用最小代價重構你的"重複查詢條件"

本文將介紹如何重構項目中的重複查詢條件。提升代碼的可讀性和可維護性。先來看一段代碼,這段代碼主要就是用於查詢電商系統中跟訂單有關的信息:設計模式

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方法
}

複製代碼

而後有一個訂單結果處理系統:bash

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因此函數名稱寫的還算清晰,可是當查詢條件複雜或者老系統代碼寫的很差的時候,每每經過函數名是沒法得知具體查詢條件的,這個時候要常常讀函數源碼才知道要幹什麼。

爲了解決上述的痛點,咱們來作一番重構,這個重構咱們要解決的痛點以下:ide

  • 消除上述代碼中的重複代碼
  • 新增需求大部分能夠經過組合的形式來寫,無需新增查詢語句
  • 代碼的可讀性要好,易於維護

咱們首先針對上述的需求,來對這些查詢條件進行一次歸攏:函數

//查詢的基類
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,不要總想着用各類設計模式去優化一些很大的模塊,而是應該從小範圍的類或者函數入口,持續重構一個個小模塊,達到最終優化整個系統的目的。優化

對於本文來講,實際應用的場景大可沒必要拘泥在「查詢」 這種業務場景上,只要你發現你的類對外提供的衆多功能函數裏面有大多數重複的代碼,和組合起來的條件,那麼均可以利用這種寫法進行重構。ui

相關文章
相關標籤/搜索