1. 程式人生 > 實用技巧 >運用設計模式告別專案中大量臃腫的if else

運用設計模式告別專案中大量臃腫的if else

前言

以前寫過的一個老專案中,有這樣一個業務場景,比喻:一個外賣系統需要接入多家餐館,在外賣系統中返回每個餐館的選單列表 ,每個餐館的選單價格都需要不同的演演算法計算。

程式碼中使用了大量的if else巢狀連線,一個類中數千行程式碼(眼睛快看瞎...),而且隨著業務的擴充套件,接入的餐館會越來越多,每接入一個餐館都要增加一個 if else,滿螢幕密密麻麻的邏輯程式碼,毫無可讀性。然後前段時間進行了程式碼重構,使用了策略模式+工廠模式+反射代替了這整片的臃腫程式碼,瞬間神清氣爽。

模擬原業務程式碼

原始碼的簡單模擬實現,根據傳入的不同餐館編碼獲取對應的餐館類集合,每個餐館選單價格的演演算法都不同。每當需要新接入一家餐館時,都需要在此增加一個if else,中間加入一大長串的處理邏輯,當餐館越來越多的時候,程式碼就變得越來越沉重,維護成本高。

public List server(String hotelCode) {
if ("HotelA".equals(hotelCode)) {
//獲取資料
List<HotelA> hotelList = new ArrayList<HotelA>() {
{
add(new HotelA("爆炒腰子", 100d, 0.8, null));
add(new HotelA("紅燒腰子", 200d, 0.8, null));
add(new HotelA("腰子刺身", 300d, 0.8, null));
}
};
//邏輯計算 最終價格 = 原價 * 折扣
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() * v.getDiscount()));
return hotelList; } else if ("HotelB".equals(hotelCode)) {
//獲取資料
List<HotelB> hotelList = new ArrayList<HotelB>() {
{
add(new HotelB("蘭州拉麵", 100d, 10d, null));
add(new HotelB("落魄後端線上炒粉", 200d, 20d, null));
}
};
//邏輯計算 最終價格 = 原價 - 優惠
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() - v.getPreferential()));
return hotelList; } else if ("HotelC".equals(hotelCode)) {
//獲取資料
List<HotelC> hotelList = new ArrayList<HotelC>() {
{
add(new HotelC("祕製奧利給", 1000d, 0.6, 20d, null));
add(new HotelC("老八辣醬", 2000d, 0.6, 10d, null));
}
};
//邏輯計算 最終價格 = 原價 * 折扣 - 服務費
hotelList.parallelStream().forEach(v -> v.setFinalPrice(v.getPrice() * v.getDiscount() - v.getTip()));
return hotelList;
}
return new ArrayList();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelA { //菜品名
private String menu; //原價
private Double price; //折扣
private Double discount; //最終價格 = 原價 * 折扣
private Double finalPrice;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelB { //菜品名
private String menu; //原價
private Double price; //優惠
private Double preferential; //最終價格 = 原價 - 優惠
private Double finalPrice;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HotelC { //菜品名
private String menu; //原價
private Double price; //折扣
private Double discount; //服務費
private Double tip; //最終價格 = 原價 * 折扣 - 服務費
private Double finalPrice;
}

策略模式+工廠模式+反射

由上述程式碼首先抽離出一個介面,if else中的業務邏輯最終都是返回一個列表

/**
* 餐館服務介面
*/
public interface HotelService { /**
* 獲取餐館選單列表
* @return
*/
List getMenuList();
}

把每個分支的業務邏輯封裝成實現類,實現HotelService介面

public class HotelAServiceImpl implements HotelService {

    /**
* 邏輯計算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() * v.getDiscount()))
.collect(Collectors.toList());
} /**
* 獲取資料
* @return
*/
public List<HotelA> initList() {
return new ArrayList<HotelA>() {
{
add(new HotelA("爆炒腰子", 100d, 0.8, null));
add(new HotelA("紅燒腰子", 200d, 0.8, null));
add(new HotelA("腰子刺身", 300d, 0.8, null));
}
};
}
}
public class HotelBServiceImpl implements HotelService {

    /**
* 邏輯計算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() - v.getPreferential()))
.collect(Collectors.toList());
} /**
* 獲取資料
* @return
*/
public List<HotelB> initList() {
return new ArrayList<HotelB>() {
{
add(new HotelB("蘭州拉麵", 100d, 10d, null));
add(new HotelB("落魄後端線上炒粉", 200d, 20d, null));
}
};
}
}
public class HotelCServiceImpl implements HotelService {

    /**
* 邏輯計算 返回集合
* @return
*/
@Override
public List getMenuList() {
return initList().parallelStream()
.peek(v -> v.setFinalPrice(v.getPrice() * v.getDiscount() - v.getTip()))
.collect(Collectors.toList());
} /**
* 獲取資料
* @return
*/
public List<HotelC> initList() {
return new ArrayList<HotelC>() {
{
add(new HotelC("祕製奧利給", 1000d, 0.6, 20d, null));
add(new HotelC("老八辣醬", 2000d, 0.6, 10d, null));
}
};
}
}

這樣就是一個簡單的策略模式了,但是現在要呼叫不同的實現類中的getMenuList方法,好像還是離不開if else,那麼現在就需要用工廠模式把所有實現類包裝起來。

先定義一個列舉類,裡面是各餐館的code

public enum HotelEnum {

    HOTEL_A("HotelA"),
HOTEL_B("HotelB"),
HOTEL_C("HotelC"),; private String hotelCode; /**
* 返回所有餐館編碼的集合
* @return
*/
public static List<String> getList() {
return Arrays.asList(HotelEnum.values())
.stream()
.map(HotelEnum::getHotelCode)
.collect(Collectors.toList());
} HotelEnum(String hotelCode) {
this.hotelCode = hotelCode;
} public String getHotelCode() {
return hotelCode;
} }

接下來定義一個服務工廠,在靜態塊中利用反射機制把所有服務實現類動態載入到HOTEL_SERVER_MAP中,然後實現一個對外的獲取對應服務的方法

這裡有幾個需要注意的地方:

1.由於包名是寫死的,那麼所有實現HotelService的實現類都需要放在固定的包下

2.類名的格式也是固定的,即列舉類中的hotelCode + "ServiceImpl"

/**
* 服務工廠類
*/
public class HotelServerFactory {
/**
* 類路徑目錄
*/
private static final String CLASS_PATH = "com.tactics.service.impl."; /**
* 服務實現字尾
*/
private static final String GAME_SERVICE_SUFFIX = "ServiceImpl"; private static final Map<String, HotelService> HOTEL_SERVER_MAP = new ConcurrentHashMap<>(); /**
* 初始化實現類到COMPANY_SERVER_MAP中
*/
static {
HotelEnum.getList().forEach(v -> {
String className = CLASS_PATH + v + GAME_SERVICE_SUFFIX;
try {
HOTEL_SERVER_MAP.put(v, (HotelService) Class.forName(className).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
});
} /**
* 獲取餐館服務實現
*
* @param hotelCode
* @return
*/
public static HotelService getHotelServerImpl(String hotelCode) {
return HOTEL_SERVER_MAP.get(hotelCode);
}
}

這裡有一個問題,如果你的服務實現類是交給Spring容器管理的,裡面有注入Mapper等等,使用反射的方式new出來的話,其中的屬性是沒有值的。

Spring容器就相當於是一個工廠了,可以直接從Spring上下文中獲取。

/**
* 服務工廠類
*/
public class HotelServerFactory {
/**
* 類路徑目錄
*/
private static final String CLASS_PATH = "com.tactics.service.impl."; /**
* 服務實現字尾
*/
private static final String GAME_SERVICE_SUFFIX = "ServiceImpl"; /**
* 獲取餐館服務實現
*
* @param hotelCode
* @return
*/
public static HotelService getHotelServerImpl(String hotelCode) {
Class clazz = Class.forName(CLASS_PATH + hotelCode + GAME_SERVICE_SUFFIX);
String className = hotelCode + GAME_SERVICE_SUFFIX;
return (HotelService) ApplicationConfig.getBean(className, clazz);
}
}

最終的呼叫

public List server(String hotelCode) {
//獲取對應的服務
HotelService hotelService = HotelServerFactory.getCompanyServerImpl(hotelCode);
//獲取經過邏輯計算後返回的集合列表
return hotelService.getMenuList();
}

怎麼樣,是不是覺得可讀性,複用性和擴充套件性都大大提高了,業務擴充套件需要新加一個餐館的時候,只需要在列舉類中加一個hotelCode,然後定義一個實現類實現HotelService介面就好了,這個Demo也讓我們知道了策略模式和工廠模式在實際專案中的應用場景。