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

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

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

相关推荐
baiduopenmap10 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish17 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
小牛itbull21 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i30 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_32 分钟前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
guokanglun38 分钟前
空间数据存储格式GeoJSON
前端
GIS瞧葩菜41 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium
ZBY520311 小时前
【Vue】 npm install amap-js-api-loader指南
javascript·vue.js·npm
猫爪笔记1 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html