我們在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”,獲取更多的學習內容