Abstruct

TypescriptでDecision Table(決定表) を作ってみたので、載せておきます。

決定表は、「条件」と「その条件のときの値」を関連付けて定義できるユーティリティとして便利なのですが、なかなかライブラリとして世の中に転がっておらず、、今回自作してみました。


目次


1. About DecisionTable

決定表については、以下の記事などを参照してください。

2. Source Code

2.1. Dicision Table

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
class DecisionTable<T, R> {
  conditionCheckers: ConditionChecker<T, R>[] = []

  run = (settings: T) => {
    return this.conditionCheckers.find((x) => x.check(settings))?.result
  }
}

class ConditionChecker<T, R> {
  private _result: R
  private _predicates: ((x: T) => boolean)[]

  set result(result: R) {
    this._result = result
  }

  get result(): R {
    return this._result
  }

  set predicates(predicates: ((x: T) => boolean)[]) {
    this._predicates = predicates
  }

  get predicates(): ((x: T) => boolean)[] {
    return this._predicates
  }

  check(settings: T): boolean {
    return this.predicates.every((f) => f(settings))
  }
}
export interface DecisionTableConditionBuilder<T, R> {
  when(...predicates: ((x: T) => boolean)[]): DecisionTableActionBuilder<T, R>
}
export interface DecisionTableActionBuilder<T, R> {
  when(...predicates: ((x: T) => boolean)[]): DecisionTableActionBuilder<T, R>
  then(result: R): DecisionTableEntryBuilder<T, R>
}
export interface DecisionTableEntryBuilder<T, R> {
  entry(): DecisionTableInstanceBuilder<T, R>
}
export interface DecisionTableInstanceBuilder<T, R> {
  when(...predicates: ((x: T) => boolean)[]): DecisionTableActionBuilder<T, R>
  build(): DecisionTable<T, R>
}
export class DecisionTableBuilder<T, R> {
  decisionTable: DecisionTable<T, R> = new DecisionTable()
  tmpConditionChecker: ConditionChecker<T, R>

  public when(...predicates: ((x: T) => boolean)[]): DecisionTableBuilder<T, R> {
    this.tmpConditionChecker = new ConditionChecker<T, R>()
    this.tmpConditionChecker.predicates = [...predicates]
    return this
  }
  public then(result: R): DecisionTableBuilder<T, R> {
    this.tmpConditionChecker.result = result
    return this
  }
  public entry(): DecisionTableBuilder<T, R> {
    this.decisionTable.conditionCheckers.push(this.tmpConditionChecker)
    return this
  }
  public build(): DecisionTable<T, R> {
    return this.decisionTable
  }
}
export function createBuilder<T, R>(): DecisionTableConditionBuilder<T, R> {
  return new DecisionTableBuilder<T, R>()
}

2.2. Test Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { createBuilder } from '@/common/utils/DecisionTable'

interface State {
  field1: number
  field2: string
}

test('正常系', () => {
  const builder = createBuilder<State, string>()
  const decisionTable = builder
    .when(
      (_) => _.field1 === 100,
      (_) => _.field2 === 'test'
    )
    .then('return sample')
    .entry()
    .build()
  
  // hit
  expect(decisionTable.run({ field1: 100, field2: 'test' })).toBe('return sample')

  // not hit ( return undefined )
  expect(decisionTable.run({ field1: 0, field2: 'hogehoge' })).toBe(undefined)
})