ES6 组合模式 实现前端设置复杂条件进行数据库查询

个人网站:aijianli.site/可以免费在线制作简历,提供PDF下载,方便快捷。

公司业务需要,需要用户可以在前端自定义查询条件对数据库数据进行查询汇总。并且需要支持较为复杂的条件设置。因此公司前同事开发了一个前端选择数据库字段设置条件的组件,但是代码极其复杂和难以维护,在我接手后需要做新增功能时根本无从下手,因此对其进行了完全的重构。详见:《如何写出让同时崩溃的代码》 。而这篇是对其中核心部分代码的重构的分享

一、条件格式

前后端通过指定统一的条件格式,由前端将条件设置转换成如下固定的格式,条件可以层层嵌套,层级不限,后端将条件解析成sql(这部分后端也是我来完成的,同样用了组合模式):

条件分为两种类型,条件组和单个条件,条件组可以包含多个条件,这样条件就可以无限嵌套,设置比较复杂的条件进行数据库查询。

js 复制代码
{
    type: String, // 条件类型 可选值 group(条件组)或 condition(具体条件)
    setType: String, // 条件格式类型,可选值filedToValue、fieldToField 分别表示指标和值生成的条件,指标和指标生成的条件。type 为 condition 时使用
    field: Object, // 字段信息
    logic: String, // 当type 为 condition 时使用,可选值有 =、>、>=、<、<= 、<> 、like、null等,支持大部分数据库支持的逻辑符,当然这只是一个表示,后端需要更加约定解析成为相应
    vlaue: String | Number | Object, // filedToValue时为一个值 String 或者 Number,fieldToField时是一个指标
    relation: String, // 条件关系 可选值 or、and  表示当前条件与后一个条件的关系,当当前条件是最后一个或者是条件组中的最后一个是,relation不生效。
    conditions: Array, // type为 group时使用,条件组中具体的条件列表
    nagation: Boolean // 条件是否取反
}

最终格式如下:

js 复制代码
[
    {
      type: "group",
      relation: "or",
      conditions: [
                {
                    type: "condition",
                    field: {
                      name: "员工状态",
                      id: 12345678
                    },
                    logic: "=",
                    val: 1,
                    relation: "and"
                 },
                 {
                    type: "condition",
                    field: {
                      name: "入职时间",
                      id: 12345678
                    },
                    logic: "<",
                    val: "2011-07-09",
                    relation: "and"
                }
            ]
        },
        {
          type: "condition",
          field: {
                    name: "入职时间",
                    id: 12345678
                },
          logic: "<",
          val: "2001-07-09",
          relation: "and"
    }
]

二、前端设计

2.1 原本的设计

原本的代码是直接从 dom 中取数据,导致将 dom 设置的条件处理成约定的条件格式时极其复杂,很多的判断和 dom 操作,阅读性极差,可维护性极差。

2.2 使用组合模式设计

先上类图吧。

以上的类设计将条件数据和 dom 分开,使用 ConditionGroup 和 ConditionData 来进行数据的存储,页面上的操作与 ConditionGroup 或 ConditionData 进行关联,这样的好处就是在生成约定格式的条件时,不需要任何 dom 的操作,只需要关注于将 ConditionGroup 或 ConditionData 中存的数据处理成条件格式即可。并且条件数据的每个实现类单独实现自身的 getCondition方法即可,外部生成条件时只需要直接调用对象的 getCondition 方法,而不需要关心具体的实现。另外如果增加了新的条件类型,直接写新的实现类即可,也就是开闭原则。

2.3 代码实现

父类

将所有类都共有的属性抽到父类中,其中 id 和 dataId 是用于和 dom 进行数据绑定的,这里不多赘述。type、relation、nagation 是所有条件或者条件组都有的属性。由于ES6 没有抽象方法和抽象类的概念,此父类中不会定义抽象方法

js 复制代码
/**
* 公共的父类
*/
class AbstractConditionData{
    constructor(id,dataId,type,relation = 'and',nagation = false){
        this.id = id;
        this.dataId =  dataId;
        this.type = type;
        this.relation = relation;
        this.nagation = nagation;
    }
}

条件数据类

条件数据类抽象出单个条件的公共属性,这里只是简单将 setType 抽象出来。

js 复制代码
import AbstractConditionData from './AbstractConditionData.js'

/**
* 条件数据的公共父类
*/
class ConditionData extends AbstractConditionData {
    /**
    * 继承此类的子类,必定是单个条件,因此条件类型固定为 condition
    */
    constructor(id, dataId, relation = "and", nagation = false, setType) {
        super(id, dataId, "condition", relation, nagation);
        this.setType = setType;
    }
}

指标和值生成的条件实现

js 复制代码
import ConditionData from "./ConditionData.js";

/**
* 指标和值创建的条件类
*/
class FieldToValueConditionData extends ConditionData {
    constructor(id, dataId, relation = "and", nagation = false) {
        super(id, dataId, relation, nagation, "filedToValue");
        // dom 中变化时会将数据保存到下面的对象中
        // firstItem 是用户设置条件的字段信息
        // loginItem 是逻辑符项
        // secondItem 是保存值的项
        this.firstItem = {};
        this.loginItem = {};
        this.secondItem = {};
    }
    
    /**
    * 实现一下生成固定格式的条件
    */
    getCondition() {
        let condition = {
            type: this.type,
            relation: this.relation,
            nagation: this.nagation,
            logic: this.loginItem.value,
            field: {},
            value: null
        }
        // 只保留有用的内容,保存所有信息json会比较大
        // 只是简单实现,省略掉了验证
        condition.field.id = this.firstItem.id;
        condition.field.name = this.firstItem.name;
        condition.field.type = this.firstItem.type;
        
        condition.value = this.secondItem.value;
        
        return condition;
    }
     
}

指标和指标生成的条件实现

js 复制代码
import ConditionData from "./ConditionData.js";

/**
* 指标和值创建的条件类
*/
class FieldToFieldConditionData extends ConditionData {
    constructor(id, dataId, relation = "and", nagation = false) {
        super(id, dataId, relation, nagation, "filedToField");
        // dom 中变化时会将数据保存到下面的对象中
        // firstItem 是用户设置条件的字段信息
        // loginItem 是逻辑符项
        // secondItem 是保存值的项
        this.firstItem = {};
        this.loginItem = {};
        this.secondItem = {};
    }
    
    /**
    * 实现一下生成固定格式的条件
    */
    getCondition() {
        let condition = {
            type: this.type,
            relation: this.relation,
            nagation: this.nagation,
            logic: this.loginItem.value,
            field: {},
            value: {}
        }
        // 只保留有用的内容,保存所有信息json会比较大
        // 只是简单实现,省略掉了验证
        condition.field.id = this.firstItem.id;
        condition.field.name = this.firstItem.name;
        condition.field.type = this.firstItem.type;
        
        condition.value.id = this.secondItem.id;
        condition.value.name = this.secondItem.name;
        condition.value.type = this.secondItem.type;
        
        return condition;
    }
     
}

条件组类

js 复制代码
/**
* 条件组的实现
*/
import AbstractConditionData from './AbstractConditionData.js'

class ConditionGroup extends AbstractConditionData {
    constructor(id, dataId, relation, nagation) {
        super(id, dataId, "group", relation, nagation);
        this.children = [];
    }
    
    /**
    * 往条件组中加一个条件
    * condition: AbstractConditionData
    */
    addCondtion(condition) {
        this.children.push(condition)
    }
    
    /**
    * 实现一下生成固定格式的条件
    */
    getCondition() {
        let conditionGroup = {
            type: this.type,
            relation: this.relation,
            nagation: this.nagation,
            conditions: []
        }
        // 这里只是展示简单的实现,并没有做一下校验和判断
        this.children.forEach(condition => {
            conditionGroup.conditions.push(condition.getCondition());
        })
        return conditionGroup;
    }
}

三、说明

从类图可以看出,实际情况是组合模式和简单工厂模式组合使用,并且条件中的项是聚合了 AbstractItem ,这些非核心的设计这里就不展开了,本文只是简单介绍了组合模式在前端的使用,dom 部分没有写在其中,大家知道如何用组合模式就好了。

啰嗦一句,面向对象的编程方式确实可以让代码更易于维护,当然必须是要做好抽象和封装,并且需要熟悉面向对象的程序设计原则:单一职责原则、开闭原则、里氏替换原则、依赖倒换原则等。在此基础上再学学设计模式,知道哪些场景下可以使用哪些设计原则来解决问题就更好啦!

前端也是需要设计模式、数据结构与算法才能写出更好的代码的。

相关推荐
编程猪猪侠25 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞29 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A1 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js
@大迁世界1 小时前
第7章 React性能优化核心
前端·javascript·react.js·性能优化·前端框架