前提・実現したいこと
入力データA[]
をデータB[]
に、さらにデータB[]
をデータC[]
に変換して、最終的にC[]
を出力するという処理を行いたいです。ここで条件は以下の通りです。
- 開発はTypeScriptで行う
- データのサイズは大きく変換処理にも時間がかかるため、非同期で行う (実際はWebWorkerを用いる)
- ユーザーは変換処理中に関わらず任意のタイミングでデータ
A
を何個でも入力することができる - 処理順を保証する必要がある (入力順と出力順が同じ)
- データはいくつかまとめて処理をする (ex. 1000個の
A
を入力すると、100個ずつに分割されて順番に処理される) - 変換
A
→B
が全て終わってから変換B
→C
を行うのではなく、例えば変換A
→B
が100個終わったら変換B
→C
を100個...と逐次的に行う (データA
の最初の入力時からデータC
の最初の出力までのレスポンスをできるだけ短くしたいため) - 変換
B
→C
が処理されている間にも変換A
→B
を処理したい
このような場合、クラス設計(アーキテクチャ?)はどのようにしたら良いのでしょうか。将来的に機能の追加・修正が行われるため、できるだけ保守性の高いコードにしたいと考えています。
次の方法はどうか
初心者ながら次のような方法を考えたのですが、問題点などありますでしょうか。
継承の必要性についても知りたいです。
こちらにもコードを記載しています。Playground
TypeScript
1// アウトプットのあるノード 2interface ISourceNode<Out> { 3 connect(nextNode: IDestinationNode<Out>): void 4} 5 6// インプットのあるノード 7interface IDestinationNode<In> { 8 post(data: In[]): void 9} 10 11interface IConverterNode<In, Out> extends ISourceNode<Out>, IDestinationNode<In> { 12} 13 14// データInを受け取ってOutに変換するような基底クラス 15class ConverterNode<In, Out> implements IConverterNode<In, Out> { 16 private queue: In[] = [] 17 private isConverting = false // 変換処理中か 18 private batchSize: number // まとめて送るデータの数 19 private nextNode!: IDestinationNode<Out> 20 21 constructor(batchSize: number) { 22 this.batchSize = batchSize 23 } 24 25 // ノードを繋げる 26 connect(nextNode: IDestinationNode<Out>) { 27 this.nextNode = nextNode 28 } 29 30 // データTを受け取る 31 post(data: In[]): void { 32 this.queue.push(...data); // 受け取ったデータをキューに保持 33 34 // 変換中でなければ変換を開始する (順序を保証するため) 35 if (!this.isConverting && this.queue.length >= this.batchSize) { 36 const batch = this.queue.splice(0, this.batchSize) 37 this.isConverting = true 38 this.convert(batch) 39 } 40 } 41 42 // 変換処理を行う 43 protected convert(data: In[]): void { 44 } 45 46 // 変換されたデータを次のノード(IConverterNodeまたはIDestinationNode)に送る 47 protected converted(data: Out[]) { 48 this.nextNode.post(data) 49 50 // キューにまだ変換できるデータが存在するならconvertする 51 if (this.queue.length >= this.batchSize) { 52 const batch = this.queue.splice(0, this.batchSize) 53 this.convert(batch) 54 } else { 55 this.isConverting = false 56 } 57 } 58}
TypeScript
1type A = number 2type B = number 3 4// データAをデータBに変換するクラス 5class ABConverter extends ConverterNode<A, B> { 6 constructor(batchSize: number) { 7 super(batchSize) 8 } 9 10 // 変換処理を行う 11 protected convert(data: A[]): void { 12 new Promise<B[]>(resolve => { 13 // 何かしらの重い処理 14 setTimeout(() => { 15 resolve(data.map(a => a * 2)) 16 }, Math.random()*100) 17 }).then(dataB => { 18 // 変換されたらconvertedを呼ぶ 19 this.converted(dataB) 20 }) 21 } 22} 23 24// 入力ノード 25class SourceNode implements ISourceNode<A> { 26 private nextNode!: IDestinationNode<B> 27 28 connect(nextNode: IDestinationNode<A>) { 29 this.nextNode = nextNode 30 } 31 32 // データをConverterに送信 33 send(data: A[]) { 34 this.nextNode.post(data) 35 } 36} 37 38// 出力ノード (変換されたデータCがここにたどり着く) 39class DestinationNode implements IDestinationNode<B> { 40 post(data: B[]) { 41 console.log(data) 42 } 43} 44 45const source = new SourceNode() 46const ab = new ABConverter(3) 47const ab2 = new ABConverter(3) 48const dest = new DestinationNode() 49 50// source -> ab -> ab2 -> dest とデータが流れる 51source.connect(ab) 52ab.connect(ab2) 53ab2.connect(dest) 54 55source.send([1, 2, 3, 4, 5, 6])
このコードの場合、入力[1, 2, 3, 4, 5, 6]
を[1, 2, 3]
[4, 5, 6]
に分割し、それぞれに対して各要素を2倍するという変換を2回施しているため、コンソールには
[4, 8, 12] [16, 20, 24]
と表示されます。