zoukankan      html  css  js  c++  java
  • 运用设计模式告别项目中大量臃肿的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 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也让我们知道了策略模式和工厂模式在实际项目中的应用场景。

  • 相关阅读:
    浏览器缓存机制
    es6笔记(6) Iterator 和 for...of循环
    es6笔记(3.1)三个点的“...”的作用
    es6笔记(5)Map数据结构
    es6笔记(4) Set数据结构
    SpringBoot事件监听
    SpringBoot入门
    五大常用算法之三贪心算法
    五种常用算法之二:动态规划算法
    五大常用算法之一:分治算法
  • 原文地址:https://www.cnblogs.com/-tang/p/13226887.html
Copyright © 2011-2022 走看看