如何使用 TypeScript 构建灵活的可扩展过滤器?

How to form flexible expandable filter with TypeScript?

我需要使用运算符 'OR' 和 'AND' 使用 TypeScript 构建一个通用过滤器。也应该有一种使用范围和多选过滤器的方法。 但是我是 OOP 的新手,无法理解我应该如何使用 classes 来完成它。 对后端的过滤器请求应该看起来像这样,如果有新类型,就必须有一种方法来扩展它们:

[
  { field: 'id', operator: '=', value: 12 },
  'OR',
  { field: 'name', operator: '=', value: 'testName' },
  'OR',
  [
    { field: 'age', operator: '>=', value: '20' },
    'AND',
    { field: 'age', operator: '=<', value: '30' }
  ]
]

我形成了一个看起来像这样的对象数组,并提供给形成过滤器的 class:

[
    {
        field: 'id',
        type: 'default',
        value: 12
    },
    {
        field: 'id2',
        type: 'default',
        value: 13
    },
    {
        field: 'name',
        type: 'default',
        value: 'yulya'
    },
    {
        field: 'age',
        type: 'range',
        value: '20 - 30'
    }
]

class FilterParameters 看起来像这样,但是它不可扩展:

export class FilterParameters {
    filter: (string | object | number)[]
    constructor() {
        this.filter = []
    }
    createFilter(receivedFields: (object)[]) {
        let filterArr:(object)[] = []
        let defaultFilter = receivedFields.filter((item)  => item.type === 'default')
        let rangeFilter = receivedFields.filter(item => item.type === 'range')
        defaultFilter.forEach((item:any) => {
            let defaultFilterObj = {
                field: item.field,
                operator: '=',
                value: item.value
            }
            filterArr.push(defaultFilterObj)
        })
        rangeFilter.forEach((item:any) => {
            let conditionArr = []
            let startCondition = item.value.split(' ')[0]
            let endCondition = item.value.split(' ').pop()
            let objStartCondition = {
                field: item.field,
                operator: '>=',
                value: startCondition
            }
            let objEndCondition = {
                field: item.field,
                operator: '=<',
                value: endCondition
            }
            conditionArr.push(objStartCondition)
            conditionArr.push('AND')
            conditionArr.push(objEndCondition)
            filterArr.push(conditionArr)
        })
        this.filter = filterArr.map((e, i) => i < filterArr.length - 1  ? [e, 'OR'] : [e]).reduce((a, b) => a.concat(b))
        return this.filter
    }
}

如何创建一个 class 形成过滤器并易于扩展?

如果我从头开始设计它,我会更改很多东西,因为您当前的方法有很多限制。例如,输入数组总是使用 'OR' 而无法使用 'AND' 或创建分组。

我假设您的输入格式和输出格式是固定的,我们的任务只是创建一种更可扩展的方式来从输入映射到输出。我做的第一件事是定义这些类型。

输入的每个过滤器看起来像这样,我们希望收到它们的数组。

interface InputFilter {
    field: string;
    type: string;
    value: any;
}

由于嵌套,输出更复杂。

interface OutputFilterElement {
    field: string;
    operator: string;
    value: any;
}

type Joiner = 'OR' | 'AND';

type OutputFilterArray = Array<OutputFilterElement | Joiner | OutputFilterArray>

type OutputFilter = OutputFilterElement | OutputFilterArray;

我们希望解析成为一个可扩展的系统,允许添加新的输入类型并将它们映射到它们的输出。如果我们要添加一个新类型,我们需要什么信息?我们需要知道它匹配的 type 字符串以及它如何将这种类型的输入对象映射到输出。我们可以把它写成 interface.

interface TypeParser {
    type: string;
    parse: (input: InputFilter) => OutputFilter;
}

我们希望 FilterParameters class 只知道 TypeParser 接口,而不知道或不关心该接口的具体实现。所以我们可以通过将任意数量的 TypeParser 对象作为参数传递给 constructor.

来实例化这个 class

class 有一个 public 方法 parse 来根据其可用的类型解析器解析一组输入过滤器。它寻找匹配的解析器,然后委托实际解析。

export class FilterParameters {
    private readonly typeParsers: TypeParser[];

    /**
     * construct an isntance of the FilterParser by passing the TypeParsers to handle specific types
     */
    constructor(...typeParsers: TypeParser[]) {
        this.typeParsers = typeParsers;
    }

    /**
     * helper function parses each element of the array
     * might throw an error
     */
    private parseOne(input: InputFilter): OutputFilter {
        // find the parser for this type
        const parser = this.typeParsers.find(p => p.type === input.type);
        if (!parser) {
            throw new Error(`no filter found for type ${input.type}`);
        }
        return parser.parse(input);
    }

    /**
     * handle an array of individual filters by assuming an 'OR' relationship
     */
    public parse(input: InputFilter[]): OutputFilter {
        // use flatMap to insert 'OR' between elements
        return input.flatMap(
            (condition, i) => {
                const parsed = this.parseOne(condition);
                return i === 0 ? [parsed] : ['OR', parsed]
            }
        );
        // do we want to catch errors here? or allow them to be thrown?
    }
}

如果不需要任何类型的实例,我认为使用 classes 是没有意义的,所以我正在创建 TypeParser 的具体实现作为实现接口的对象.

const EqualityParser: TypeParser = {
    type: 'default',
    parse: ({ field, value }: InputFilter): OutputFilter => {
        return ({
            field,
            // operator is always equals for default filter
            operator: '=',
            value
        });
    }
}

const RangeParser: TypeParser = {
    type: 'range',
    parse: ({ field, value }: InputFilter): OutputFilter => {
        // split based on hyphen with optional spaces before and after
        const [min, max] = value.split(/\s*-\s*/);
        // are these always numbers? should we use parseFloat?
        return ([{
            field,
            operator: '>=',
            value: min
        },
            'AND',
        {
            field,
            operator: '<=',
            value: max
        }]);
    }
}

为了使用这两个解析器解析您的输入,您可以这样写:

const filterer = new FilterParameters(EqualityParser, RangeParser);
const output = filterer.parse(input);

Typescript Playground Link