我們在web開發中,經常使用數據庫表中的字段作為“標記”來表示多個“狀態”,比如:
我們就以某寶的在線購物流程為例進行分析。在訂單表中,使用zt字段來表示定單的狀態,常見的狀態就有:
狀態碼 | 狀態說明 |
---|---|
0 | 待付款 |
1 | 待發貨 |
2 | 待收貨 |
3 | 待評價 |
4 | 售后 |
當我們想按條件查詢各個類型的訂單的時候,只需要一個接口,在前端傳入相應的狀態碼就可以了。在dao層大概也就是通過如下的語句進行查詢:
select * from orders where zt = #{zt}
如何才能有很高的擴展性?
假設有這么幾個“不成需求的需求”:
- 我想讓待收貨的訂單按照訂單發貨時間或者預計送達時間排序,其他的暫且按照訂單創建時間排序吧
- 想將“待收貨”的狀態區分開,分為“用戶未收到貨”和“用戶收到貨但是未點擊確認收貨按紐”兩種狀態
常規方式如何解決?
-
需求一(不同的狀態處理方式不同):
這個很容易的,在sevice層添加一個判斷就可以,其他的代碼不用改,代碼如下:
// 2 表示待收貨
if(zt == 2){
//按照需求,按照訂單發貨時間或者預計送達時間排序
}else{
//其他狀態的訂單,全部按照訂單創建時間排序
}
上邊這個代碼的修改量已經很小了,但是如果我要把和種不同的狀態訂單全部按照不同的排序方式排序呢?你可能會寫如下代碼
if(zt==0){
// 待付款的訂單處理代碼...
}else if(zt==1){
}else if(zt==2){
}else if(zt==3){
}else if(zt==4){
}
上邊代碼太low了,有些小伙伴可能會使用switch進行優化(這里就不寫代碼了,因為和上邊並沒有任何區別)。
-
需求二(添加一個新的狀態表示):
這也很easy啊,直接在上邊的if-else或者switch代碼中添加新的狀態判斷不就好了。
思考如何干掉if-else?
上邊的方式可以完成我們的需求,但是有以下幾點不足:
1. 面對“各種各樣奇怪的需求”,我們要頻繁地修改上邊的代碼,時間久了,豈不成了渣渣。甚至我們自己都不願意再去看這些代碼了;
2. 如果新增加一個狀態表示,也就是給zt字段新的狀態含義表示,我們又要添加if-else,這太復雜了。
使用策略模式來解決if-else的問題
是的,就是使用策略模式來解決進行太多的狀態判斷代碼就是一個好辦法。比如,就上邊每一個if-else中的代碼抽成一個類或者方法進行處理。
主要的代碼我就不寫了,因為下邊才是我們的主菜,這里說的這種方式只能解決if-else里邊的代碼復雜問題,將代碼進行一定程度上的解耦。但並沒有實質地解決if-else的問題,而且這也是網上大多數的解決辦法。
如果對策略模式不太了解的小伙伴,可以看下這篇文章,不看也沒關系,在下邊你會看到怎么用的。策略模式的學習之道
嘗試使用Spring來配合策略模式
程序設計的一大原則“對擴展開放,對修改關閉”,定義一個接口類,用來查詢不同狀態的訂單列表。如下:
public interface OrderService {
/**
* 查詢對應狀態的訂單列表
* @param zt
* @return
*/
List<Order> getOrderList(String zt);
}
然后根據不同的訂單狀態創建不同的實現類,比如,“待付款”的訂單查詢類如下:
雖然以下的命名方式屬於錯誤示范,但是卻能很好地理解
@Service("orderServiceDfk") // 這個命名確實很不友好,但是我相信你能理解哈
public class PendingParymentOrderSeviceImpl implements OrderService {
/**
* 查詢待付款的訂單列表
*
* @param zt
* @return
*/
@Override
public List<Order> getOrderList(String zt) {
//這里要利用dao層從數據庫中查詢出來相應的訂單列表
return null;
}
看了這兩個類的代碼,我相信小伙伴們應該能理解了要怎么做了,就是根據前端傳來不同的zt值,后台使用不同的類來處理,但是我們可以通過Spring來完全取掉if-else。
我們的controller層代碼如下:
@RestController
public class OrderController {
private String orderServiceBeanNamePrefix = "orderService";
@RequestMapping("getOrderList/{zt}")
public List<Order> getOrderList(@PathVariable("zt") String zt) {
//獲取對應的處理狀態的bean來處理
//就通過這樣一句代碼,完全解決了if-else的判斷邏輯
OrderService orderService = (OrderService) SpingContext.getBean(orderServiceBeanNamePrefix + zt);
List<Order> orderList = orderService.getOrderList();
return orderList;
}
}
上邊用了一個工具類,就是從Spring 容器中獲取相應的bean,代碼如下:
/**
* 微信公眾號 “小魚與Java”
*
* 原理很簡單,我們寫的類實現這個接口,具體可以查閱Spring生命周期相關內容
* Spring會自動調用其中的setApplicationContext方法,傳入Spring容器上下文
* 我們就在這里把Spring上下文保存下來
*
* @date 2020/5/18
* @auther Lyn4ever
*/
@Component
public class SpingContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 根據name從Spring容器中獲取bean
* @param name
* @return
*/
public static Object getBean(String name){
return applicationContext.getBean(name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("我保存了Spring上下文");
applicationContext = applicationContext;
}
}
總結:
解決if-else的思路就是使用策略模式,針對不同“狀態”的訂單,使用不同的類來處理邏輯,這樣就可以很好地進行了“解耦”操作。但是,如果新增一個“狀態表示 ”,我們就要在主邏輯處添加if-else進行判斷要用哪個類來處理。
而解決這個“判斷 ”的中使用的if-else就有很多方法:抽象工廠也是一個不錯的方法。而我們使用Spring的控制反轉同樣也可以很好地解決這個問題。這么做的好處如下:
- “真正的”解決了與我們業務無關的if-else;
- 不用前后端再進行狀態的表示“約定”,之前用0表示“待付款”,1表示 “待發貨”這樣的操作,如果記錯,那一定會有大問題。現在,使用特定的字符串來表示,也就是說,前端直接傳入想要解決這個方案對應的bean,從而少去了“復雜且易出錯的約定”環節。
代碼地址:
關注微信公眾號“小魚與Java”,獲取更多的學習內容