前言
以前寫過的一個老項目中,有這樣一個業務場景,比喻:一個外賣系統需要接入多家餐館,在外賣系統中返回每個餐館的菜單列表 ,每個餐館的菜單價格都需要不同的算法計算。
代碼中使用了大量的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 HOTEL_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 + HOTEL_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上下文中獲取(怎么獲取Spring上下文對象這里就不細說了,有需要可以自行百度)。
/** * 服務工廠類 */ public class HotelServerFactory { /** * 類路徑目錄 */ private static final String CLASS_PATH = "com.tactics.service.impl."; /** * 服務實現后綴 */ private static final String HOTEL_SERVICE_SUFFIX = "ServiceImpl"; /** * 獲取餐館服務實現 * * @param hotelCode * @return */ public static HotelService getHotelServerImpl(String hotelCode) { Class clazz = Class.forName(CLASS_PATH + hotelCode + HOTEL_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也讓我們知道了策略模式和工廠模式在實際項目中的應用場景。