【设计模式】一次代码重构后对策略模式的思考

经常看到有人把软件开发和建筑行业类比,把架构师比作工程师,个人觉得还是有一定区别的,建筑行业的成品一旦落成,是不变的,而软件面向的是变化。但相同点是,如果内心有一种现在大力鼓吹的“匠人精神”,都会让作品更优美吧。建筑行业不太了解不做过多评论,作为码农一枚,如果能时常审视自己代码,反思设计的是不是美观,代码是不是流畅,相信是会写出好的作品的。而设计模式往往可以帮助我们写出更精致的作品。

【一】需求引出问题

最近需要改造一个现有的开源CRM系统,其中一段代码是下面这个样子:

    String viewName;
        switch (type) {
            case 1:
                viewName = "leadsview";
                break;
            case 2:
                viewName = "customerview";
                break;
            case 3:
                viewName = "contactsview";
                break;
            case 4:
                viewName = "productview";
                break;
            case 5:
                viewName = "businessview";
                break;
            case 6:
                viewName = "contractview";
                break;
            case 7:
                viewName = "receivablesview";
                break;
            case 8:
                viewName = "customerview";
                break;
            default:
                return R.error("type不符合要求");
        }
        String from;
        if (2 == type) {
            from = "from " + viewName + whereSb.toString() + " and owner_user_id is not null order by " + viewName + ".update_time desc";
        } else if (8 == type) {
            from = "from " + viewName + whereSb.toString() + " and owner_user_id is null order by " + viewName + ".update_time desc";
        } else {
            from = "from " + viewName + whereSb.toString() + " order by " + viewName + ".update_time desc";
        }
        if (StrUtil.isNotEmpty(basePageRequest.getJsonObject().getString("excel"))) {
            return R.ok().put("data", Db.find("select * " + from));
        }
        if (2 == type || 8 == type) {
            Integer configType = Db.queryInt("select type from 72crm_admin_customer_setting");
            if (1 == configType && 2 == type) {
                return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(),
                        "select * , (TO_DAYS(update_time) + (SELECT followup_day FROM 72crm_admin_customer_setting) - TO_DAYS(NOW())) as pool_day ,(select count(*) from 72crm_crm_business as a where a.customer_id = " + viewName + ".customer_id) as business_count", from));
            } else {
                return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(),
                        "select *,(select count(*) from 72crm_crm_business as a where a.customer_id = " + viewName + ".customer_id) as business_count", from));
            }

        }
        return R.ok().put("data", Db.paginate(basePageRequest.getPage(), basePageRequest.getLimit(), "select *", from));
    

这段代码的大致意思就是,所有获取列表信息的接口,都会走到该方法,根据type的不同,分别到不同的视图中进行查询,暂且先不说视图的性能问题,单是这满屏的switch…case…和if···else···语句,已经足够引起了我们对它的重视。

【二】策略模式介绍

官方定义

定义一组算法,将每个算法封装起来,并且可以使他们互换,属于对象行为模式。

使用时机
当一个类中的操作出现多个条件分支,就可以考虑使用策略模式,将相关条件分支移入各自具体策略类。

优缺点:

优点 缺点
算法自由切换 策略类数量可能很多
避免多重条件判断 所有策略类需要对外暴露
扩展性好

关于上述提到的缺点,我们可以通过其他设计模式进行调整,例如工厂模式,代理模式等,后面会提到。

角色:

  • Context:上下文环境角色,封装作用,屏蔽高层模块对策略和算法的直接访问
  • Strategy:抽象策略角色,定义公共策略方法,通常由一个接口或抽象类实现
  • ConcreteStrategy:具体策略角色:实现具体策略算法,封装相关算法和行为

当你读完这段话,有没有一种茫然感,仿佛刚刚读的是天书,设计模式往往就是这样。我来尝试通俗的解释一下整个过程,如果你有一个长长的 if···else··· 语句,就可以尝试把每个判断分支中的逻辑单独抽到一个类中,这就是ConcreteStrategy类;让他们实现同一个接口,就是Strategy接口;Context中有一个Strategy的引用,最终外界通过调用Context来对ConcreteStrategy访问。

下面是一个简单策略模式的UML类图:

在这里插入图片描述

思考:

借用了代理模式思路,但与代理模式存在一定的区别:策略模式封装的角色和被封装的策略类不用同一个接口;如果使用同一接口,就是代理模式。

【三】在实际项目中的扩展

在基本的策略模式中,选择哪个具体实现是由客户端对象承担,本身并没有减少客户端选择判断的压力,可以选择与简单工厂模式结合,选择哪个具体实现的事情也可以由Context承担,就减轻了客户端的职责。一般在具体策略数量超过4个时,就可以考虑使用混合模式。

在这里,由于原系统使用的是JFinal框架,而我们对他都不了解,所以实现的方式是,在context类中提供一个Map,把if···else···语句进行抽取,如果在Spring中,直接写一个接口,这个接口的实现类,会在加载的时候直接注入到上下文中,只需要根据不同的策略,取出这个Bean就可以了。

Context类部分代码:

 private static final HashMap<Integer, Class> QUERY_WRAPPER_HASH_MAP = new HashMap<>(2);

    static{
        QUERY_WRAPPER_HASH_MAP.put(EntityTypeConstant.CUSTOMER_ENTITY_TYPE, new CustomerQueryWrapper().getClass());
        QUERY_WRAPPER_HASH_MAP.put(EntityTypeConstant.ORDER_ENTITY_TYPE, new OrderQueryWrapper().getClass());
    }

 public IEntityService getService(Integer type){
        if (customerService.getType().equals(type)){
            return customerService;
        }
        else if (orderService.getType().equals(type)){
            return orderService;
        }
        throw new RuntimeException("未找到对应的type类型");
    }

客户端代码

return R.ok().put("data", entityContext.getService(type).getPageList(basePageRequest.getPage(),basePageRequest.getLimit(),entityContext.getQueryWrapper(type,jsonObjectList)));

下面是改造后的UML类图:

在这里插入图片描述

【四】总结

策略模式在虽然项目中的使用场景很多,但很少单独使用,因为它需要暴露出所有策略,选择策略还是在客户端完成,这样并没有达到最终目的。实际项目中一般通过工厂方法实现策略类的声明。

发布了23 篇原创文章 · 获赞 3 · 访问量 1854

猜你喜欢

转载自blog.csdn.net/HoyingHan/article/details/98997609