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 部分没有写在其中,大家知道如何用组合模式就好了。

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

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

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js